All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/12] PCI run-time PM support (rev. 2)
@ 2009-12-27 19:57 Rafael J. Wysocki
  2009-12-27 19:59   ` Rafael J. Wysocki
                   ` (22 more replies)
  0 siblings, 23 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2009-12-27 19:57 UTC (permalink / raw)
  To: Jesse Barnes, Matthew Garrett, Len Brown
  Cc: LKML, pm list, Alan Stern, ACPI Devel Maling List, Linux PCI,
	Oliver Neukum, Bjorn Helgaas, Shaohua Li, Francois Romieu

Hi,

The following (updated) series of patches provides preliminary run-time power
management support for PCI devices through ACPI and/or the native PCIe PME.

Some patches have been modified since the previous iteration, one patch has
been merged and there's one more.

I've tested this patchset with the native PCIe PME mechanism using the r8169
driver on the MSI Wind U-100 (see the last patch for details) and with the ACPI
mechanism using the e1000e driver on the Toshiba Portege R500 (the patch still
requires some work to be shown in public ;-)).

[1/12] - Add function for checking PME status of devices
 
[2/12] - Modify wake-up enable propagation so that it's done for PCIe devices
         too (this one is in the Jesse's tree already, but it's reproduced here
         for completness)
 
[3/12] - PCIe PME root port service driver

[4/12] - "Don't use MSIs for PME signaling" switch for PCIe
 
[5/12] - ACPI GPE refcounting, from Matthew
 
[6/12] - ACPI drivers support for GPE refcounting, from Matthew
 
[7/12] - ACPI removal of the old GPE API, from Matthew
 
[8/12] - ACPI add fields for handling run-wake devices
 
[9/12][New] - ACPI add helper function for enabling/disabling wake-up power

[10/12] - PCI / ACPI platform support for run-time power management

[11/12] - Runtime PM callbacks for the PCI bus type

[12/12] - Runtime PM support for r8169 (experimental)

If there are no objections, I think the patches [1-11/12] are ready for the
upstream.

Thanks,
Rafael

^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH 1/12] PCI PM: Add function for checking PME status of devices
  2009-12-27 19:57 [PATCH 0/12] PCI run-time PM support (rev. 2) Rafael J. Wysocki
@ 2009-12-27 19:59   ` Rafael J. Wysocki
  2009-12-27 20:00 ` [PATCH 2/12] PCI / PM: Propagate wake-up enable for PCIe devices too Rafael J. Wysocki
                     ` (21 subsequent siblings)
  22 siblings, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2009-12-27 19:59 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Linux PCI, LKML, ACPI Devel Maling List, Francois Romieu,
	pm list, Matthew Garrett

From: Rafael J. Wysocki <rjw@sisk.pl>

Add function pci_check_pme_status() that will check the PME status
bit of given device and clear it along with the PME enable bit.  It
will be necessary for PCI run-time power management.

Based on a patch from Shaohua Li <shaohua.li@intel.com>

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
 drivers/pci/pci.c |   35 +++++++++++++++++++++++++++++++++++
 drivers/pci/pci.h |    1 +
 2 files changed, 36 insertions(+)

Index: linux-2.6/drivers/pci/pci.h
===================================================================
--- linux-2.6.orig/drivers/pci/pci.h
+++ linux-2.6/drivers/pci/pci.h
@@ -49,6 +49,7 @@ struct pci_platform_pm_ops {
 extern int pci_set_platform_pm(struct pci_platform_pm_ops *ops);
 extern void pci_update_current_state(struct pci_dev *dev, pci_power_t state);
 extern void pci_disable_enabled_device(struct pci_dev *dev);
+extern bool pci_check_pme_status(struct pci_dev *dev);
 extern void pci_pm_init(struct pci_dev *dev);
 extern void platform_pci_wakeup_init(struct pci_dev *dev);
 extern void pci_allocate_cap_save_buffers(struct pci_dev *dev);
Index: linux-2.6/drivers/pci/pci.c
===================================================================
--- linux-2.6.orig/drivers/pci/pci.c
+++ linux-2.6/drivers/pci/pci.c
@@ -1180,6 +1180,41 @@ int pci_set_pcie_reset_state(struct pci_
 }
 
 /**
+ * pci_check_pme_status - Check if given device has generated PME.
+ * @dev: Device to check.
+ *
+ * Check the PME status of the device and if set, clear it and clear PME enable
+ * (if set).  Return 'true' if PME status and PME enable were both set or
+ * 'false' otherwise.
+ */
+bool pci_check_pme_status(struct pci_dev *dev)
+{
+	int pmcsr_pos;
+	u16 pmcsr;
+	bool ret = false;
+
+	if (!dev->pm_cap)
+		return false;
+
+	pmcsr_pos = dev->pm_cap + PCI_PM_CTRL;
+	pci_read_config_word(dev, pmcsr_pos, &pmcsr);
+	if (!(pmcsr & PCI_PM_CTRL_PME_STATUS))
+		return false;
+
+	/* Clear PME status. */
+	pmcsr |= PCI_PM_CTRL_PME_STATUS;
+	if (pmcsr & PCI_PM_CTRL_PME_ENABLE) {
+		/* Disable PME to avoid interrupt flood. */
+		pmcsr &= ~PCI_PM_CTRL_PME_ENABLE;
+		ret = true;
+	}
+
+	pci_write_config_word(dev, pmcsr_pos, pmcsr);
+
+	return ret;
+}
+
+/**
  * pci_pme_capable - check the capability of PCI device to generate PME#
  * @dev: PCI device to handle.
  * @state: PCI state from which device will issue PME#.

^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH 1/12] PCI PM: Add function for checking PME status of devices
@ 2009-12-27 19:59   ` Rafael J. Wysocki
  0 siblings, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2009-12-27 19:59 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Matthew Garrett, Len Brown, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu

From: Rafael J. Wysocki <rjw@sisk.pl>

Add function pci_check_pme_status() that will check the PME status
bit of given device and clear it along with the PME enable bit.  It
will be necessary for PCI run-time power management.

Based on a patch from Shaohua Li <shaohua.li@intel.com>

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
 drivers/pci/pci.c |   35 +++++++++++++++++++++++++++++++++++
 drivers/pci/pci.h |    1 +
 2 files changed, 36 insertions(+)

Index: linux-2.6/drivers/pci/pci.h
===================================================================
--- linux-2.6.orig/drivers/pci/pci.h
+++ linux-2.6/drivers/pci/pci.h
@@ -49,6 +49,7 @@ struct pci_platform_pm_ops {
 extern int pci_set_platform_pm(struct pci_platform_pm_ops *ops);
 extern void pci_update_current_state(struct pci_dev *dev, pci_power_t state);
 extern void pci_disable_enabled_device(struct pci_dev *dev);
+extern bool pci_check_pme_status(struct pci_dev *dev);
 extern void pci_pm_init(struct pci_dev *dev);
 extern void platform_pci_wakeup_init(struct pci_dev *dev);
 extern void pci_allocate_cap_save_buffers(struct pci_dev *dev);
Index: linux-2.6/drivers/pci/pci.c
===================================================================
--- linux-2.6.orig/drivers/pci/pci.c
+++ linux-2.6/drivers/pci/pci.c
@@ -1180,6 +1180,41 @@ int pci_set_pcie_reset_state(struct pci_
 }
 
 /**
+ * pci_check_pme_status - Check if given device has generated PME.
+ * @dev: Device to check.
+ *
+ * Check the PME status of the device and if set, clear it and clear PME enable
+ * (if set).  Return 'true' if PME status and PME enable were both set or
+ * 'false' otherwise.
+ */
+bool pci_check_pme_status(struct pci_dev *dev)
+{
+	int pmcsr_pos;
+	u16 pmcsr;
+	bool ret = false;
+
+	if (!dev->pm_cap)
+		return false;
+
+	pmcsr_pos = dev->pm_cap + PCI_PM_CTRL;
+	pci_read_config_word(dev, pmcsr_pos, &pmcsr);
+	if (!(pmcsr & PCI_PM_CTRL_PME_STATUS))
+		return false;
+
+	/* Clear PME status. */
+	pmcsr |= PCI_PM_CTRL_PME_STATUS;
+	if (pmcsr & PCI_PM_CTRL_PME_ENABLE) {
+		/* Disable PME to avoid interrupt flood. */
+		pmcsr &= ~PCI_PM_CTRL_PME_ENABLE;
+		ret = true;
+	}
+
+	pci_write_config_word(dev, pmcsr_pos, pmcsr);
+
+	return ret;
+}
+
+/**
  * pci_pme_capable - check the capability of PCI device to generate PME#
  * @dev: PCI device to handle.
  * @state: PCI state from which device will issue PME#.


^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH 2/12] PCI / PM: Propagate wake-up enable for PCIe devices too
  2009-12-27 19:57 [PATCH 0/12] PCI run-time PM support (rev. 2) Rafael J. Wysocki
  2009-12-27 19:59   ` Rafael J. Wysocki
@ 2009-12-27 20:00 ` Rafael J. Wysocki
  2010-01-04 23:40   ` Jesse Barnes
  2010-01-04 23:40   ` Jesse Barnes
  2009-12-27 20:00 ` Rafael J. Wysocki
                   ` (20 subsequent siblings)
  22 siblings, 2 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2009-12-27 20:00 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Matthew Garrett, Len Brown, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu

From: Rafael J. Wysocki <rjw@sisk.pl>

Having read the PM part of the PCIe 2.0 specification more carefully
I think that it was a mistake to restrict the wake-up enable
propagation to non-PCIe devices, because if we do not request
control of the root ports' PME registers via OSC, PCIe PME is
supposed to be handled by the platform, just like the non-PCIe PME.
Even if we do that, the wake-up propagation is done to allow the
devices to wake up the system from sleep states which involves the
platform anyway, so it won't hurt.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
 drivers/pci/pci-acpi.c |   10 ++--------
 1 file changed, 2 insertions(+), 8 deletions(-)

Index: linux-2.6/drivers/pci/pci-acpi.c
===================================================================
--- linux-2.6.orig/drivers/pci/pci-acpi.c
+++ linux-2.6/drivers/pci/pci-acpi.c
@@ -112,11 +112,7 @@ static bool acpi_pci_can_wakeup(struct p
 static void acpi_pci_propagate_wakeup_enable(struct pci_bus *bus, bool enable)
 {
 	while (bus->parent) {
-		struct pci_dev *bridge = bus->self;
-		int ret;
-
-		ret = acpi_pm_device_sleep_wake(&bridge->dev, enable);
-		if (!ret || pci_is_pcie(bridge))
+		if (!acpi_pm_device_sleep_wake(&bus->self->dev, enable))
 			return;
 		bus = bus->parent;
 	}
@@ -131,9 +127,7 @@ static int acpi_pci_sleep_wake(struct pc
 	if (acpi_pci_can_wakeup(dev))
 		return acpi_pm_device_sleep_wake(&dev->dev, enable);
 
-	if (!pci_is_pcie(dev))
-		acpi_pci_propagate_wakeup_enable(dev->bus, enable);
-
+	acpi_pci_propagate_wakeup_enable(dev->bus, enable);
 	return 0;
 }
 


^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH 2/12] PCI / PM: Propagate wake-up enable for PCIe devices too
  2009-12-27 19:57 [PATCH 0/12] PCI run-time PM support (rev. 2) Rafael J. Wysocki
  2009-12-27 19:59   ` Rafael J. Wysocki
  2009-12-27 20:00 ` [PATCH 2/12] PCI / PM: Propagate wake-up enable for PCIe devices too Rafael J. Wysocki
@ 2009-12-27 20:00 ` Rafael J. Wysocki
  2009-12-27 20:01 ` [PATCH 3/12] PCI PM: PCIe PME root port service driver (rev. 5) Rafael J. Wysocki
                   ` (19 subsequent siblings)
  22 siblings, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2009-12-27 20:00 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Linux PCI, LKML, ACPI Devel Maling List, Francois Romieu,
	pm list, Matthew Garrett

From: Rafael J. Wysocki <rjw@sisk.pl>

Having read the PM part of the PCIe 2.0 specification more carefully
I think that it was a mistake to restrict the wake-up enable
propagation to non-PCIe devices, because if we do not request
control of the root ports' PME registers via OSC, PCIe PME is
supposed to be handled by the platform, just like the non-PCIe PME.
Even if we do that, the wake-up propagation is done to allow the
devices to wake up the system from sleep states which involves the
platform anyway, so it won't hurt.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
 drivers/pci/pci-acpi.c |   10 ++--------
 1 file changed, 2 insertions(+), 8 deletions(-)

Index: linux-2.6/drivers/pci/pci-acpi.c
===================================================================
--- linux-2.6.orig/drivers/pci/pci-acpi.c
+++ linux-2.6/drivers/pci/pci-acpi.c
@@ -112,11 +112,7 @@ static bool acpi_pci_can_wakeup(struct p
 static void acpi_pci_propagate_wakeup_enable(struct pci_bus *bus, bool enable)
 {
 	while (bus->parent) {
-		struct pci_dev *bridge = bus->self;
-		int ret;
-
-		ret = acpi_pm_device_sleep_wake(&bridge->dev, enable);
-		if (!ret || pci_is_pcie(bridge))
+		if (!acpi_pm_device_sleep_wake(&bus->self->dev, enable))
 			return;
 		bus = bus->parent;
 	}
@@ -131,9 +127,7 @@ static int acpi_pci_sleep_wake(struct pc
 	if (acpi_pci_can_wakeup(dev))
 		return acpi_pm_device_sleep_wake(&dev->dev, enable);
 
-	if (!pci_is_pcie(dev))
-		acpi_pci_propagate_wakeup_enable(dev->bus, enable);
-
+	acpi_pci_propagate_wakeup_enable(dev->bus, enable);
 	return 0;
 }
 

^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH 3/12] PCI PM: PCIe PME root port service driver (rev. 5)
  2009-12-27 19:57 [PATCH 0/12] PCI run-time PM support (rev. 2) Rafael J. Wysocki
                   ` (3 preceding siblings ...)
  2009-12-27 20:01 ` [PATCH 3/12] PCI PM: PCIe PME root port service driver (rev. 5) Rafael J. Wysocki
@ 2009-12-27 20:01 ` Rafael J. Wysocki
  2010-01-06 21:53   ` Jesse Barnes
  2010-01-06 21:53   ` Jesse Barnes
  2009-12-27 20:02   ` Rafael J. Wysocki
                   ` (17 subsequent siblings)
  22 siblings, 2 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2009-12-27 20:01 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Matthew Garrett, Len Brown, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu

From: Rafael J. Wysocki <rjw@sisk.pl>

PCIe native PME detection mechanism is based on interrupts generated
by root ports or event collectors every time a PCIe device sends a
PME message upstream.

Once a PME message has been sent by an endpoint device and received
by its root port (or event collector in the case of root complex
integrated endpoints), the Requester ID from the message header is
registered in the root port's Root Status register.  At the same
time, the PME Status bit of the Root Status register is set to
indicate that there's a PME to handle.  If PCIe PME interrupt is
enabled for the root port, it generates an interrupt once the PME
Status has been set.  After receiving the interrupt, the kernel can
identify the PCIe device that generated the PME using the Requester
ID from the root port's Root Status register. [For details, see PCI
Express Base Specification, Rev. 2.0.]

Implement a driver for the PCIe PME root port service working in
accordance with the above description.

Based on a patch from Shaohua Li <shaohua.li@intel.com>.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
 Documentation/kernel-parameters.txt  |    6 
 drivers/pci/pcie/Kconfig             |    4 
 drivers/pci/pcie/Makefile            |    2 
 drivers/pci/pcie/pme/Makefile        |    8 
 drivers/pci/pcie/pme/pcie_pme.c      |  493 +++++++++++++++++++++++++++++++++++
 drivers/pci/pcie/pme/pcie_pme.h      |   28 +
 drivers/pci/pcie/pme/pcie_pme_acpi.c |   54 +++
 include/linux/pci.h                  |    1 
 8 files changed, 596 insertions(+)

Index: linux-2.6/drivers/pci/pcie/Kconfig
===================================================================
--- linux-2.6.orig/drivers/pci/pcie/Kconfig
+++ linux-2.6/drivers/pci/pcie/Kconfig
@@ -46,3 +46,7 @@ config PCIEASPM_DEBUG
 	help
 	  This enables PCI Express ASPM debug support. It will add per-device
 	  interface to control ASPM.
+
+config PCIE_PME
+	def_bool y
+	depends on PCIEPORTBUS && PM_RUNTIME && EXPERIMENTAL
Index: linux-2.6/drivers/pci/pcie/Makefile
===================================================================
--- linux-2.6.orig/drivers/pci/pcie/Makefile
+++ linux-2.6/drivers/pci/pcie/Makefile
@@ -11,3 +11,5 @@ obj-$(CONFIG_PCIEPORTBUS)	+= pcieportdrv
 
 # Build PCI Express AER if needed
 obj-$(CONFIG_PCIEAER)		+= aer/
+
+obj-$(CONFIG_PCIE_PME) += pme/
Index: linux-2.6/drivers/pci/pcie/pme/pcie_pme.c
===================================================================
--- /dev/null
+++ linux-2.6/drivers/pci/pcie/pme/pcie_pme.c
@@ -0,0 +1,493 @@
+/*
+ * PCIe Native PME support
+ *
+ * Copyright (C) 2007 - 2009 Intel Corp
+ * Copyright (C) 2007 - 2009 Shaohua Li <shaohua.li@intel.com>
+ * Copyright (C) 2009 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc.
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License V2.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/pcieport_if.h>
+#include <linux/acpi.h>
+#include <linux/pci-acpi.h>
+#include <linux/pm_runtime.h>
+
+#include "../../pci.h"
+#include "pcie_pme.h"
+
+#define PCI_EXP_RTSTA_PME	0x10000 /* PME status */
+#define PCI_EXP_RTSTA_PENDING	0x20000 /* PME pending */
+
+/*
+ * If set, this switch will prevent the PCIe root port PME service driver from
+ * being registered.  Consequently, the interrupt-based PCIe PME signaling will
+ * not be used by any PCIe root ports in that case.
+ */
+static bool pcie_pme_disabled;
+
+/*
+ * The PCI Express Base Specification 2.0, Section 6.1.8, states the following:
+ * "In order to maintain compatibility with non-PCI Express-aware system
+ * software, system power management logic must be configured by firmware to use
+ * the legacy mechanism of signaling PME by default.  PCI Express-aware system
+ * software must notify the firmware prior to enabling native, interrupt-based
+ * PME signaling."  However, if the platform doesn't provide us with a suitable
+ * notification mechanism or the notification fails, it is not clear whether or
+ * not we are supposed to use the interrupt-based PCIe PME signaling.  The
+ * switch below can be used to indicate the desired behaviour.  When set, it
+ * will make the kernel use the interrupt-based PCIe PME signaling regardless of
+ * the platform notification status, although the kernel will attempt to notify
+ * the platform anyway.  When unset, it will prevent the kernel from using the
+ * the interrupt-based PCIe PME signaling if the platform notification fails,
+ * which is the default.
+ */
+static bool pcie_pme_force_enable;
+
+static int __init pcie_pme_setup(char *str)
+{
+	if (!strcmp(str, "off"))
+		pcie_pme_disabled = true;
+	else if (!strcmp(str, "force"))
+		pcie_pme_force_enable = true;
+	return 1;
+}
+__setup("pcie_pme=", pcie_pme_setup);
+
+/**
+ * pcie_pme_platform_setup - Ensure that the kernel controls the PCIe PME.
+ * @srv: PCIe PME root port service to use for carrying out the check.
+ *
+ * Notify the platform that the native PCIe PME is going to be used and return
+ * 'true' if the control of the PCIe PME registers has been acquired from the
+ * platform.
+ */
+static bool pcie_pme_platform_setup(struct pcie_device *srv)
+{
+	return !pcie_pme_platform_notify(srv) || pcie_pme_force_enable;
+}
+
+struct pcie_pme_service_data {
+	spinlock_t lock;
+	struct pcie_device *srv;
+	struct work_struct work;
+	bool noirq; /* Don't enable the PME interrupt used by this service. */
+};
+
+/**
+ * pcie_pme_interrupt_enable - Enable/disable PCIe PME interrupt generation.
+ * @dev: PCIe root port or event collector.
+ * @enable: Enable or disable the interrupt.
+ */
+static void pcie_pme_interrupt_enable(struct pci_dev *dev, bool enable)
+{
+	int rtctl_pos;
+	u16 rtctl;
+
+	rtctl_pos = pci_find_capability(dev, PCI_CAP_ID_EXP) + PCI_EXP_RTCTL;
+
+	pci_read_config_word(dev, rtctl_pos, &rtctl);
+	if (enable)
+		rtctl |= PCI_EXP_RTCTL_PMEIE;
+	else
+		rtctl &= ~PCI_EXP_RTCTL_PMEIE;
+	pci_write_config_word(dev, rtctl_pos, rtctl);
+}
+
+/**
+ * pcie_pme_clear_status - Clear root port PME interrupt status.
+ * @dev: PCIe root port or event collector.
+ */
+static void pcie_pme_clear_status(struct pci_dev *dev)
+{
+	int rtsta_pos;
+	u32 rtsta;
+
+	rtsta_pos = pci_find_capability(dev, PCI_CAP_ID_EXP) + PCI_EXP_RTSTA;
+
+	pci_read_config_dword(dev, rtsta_pos, &rtsta);
+	rtsta |= PCI_EXP_RTSTA_PME;
+	pci_write_config_dword(dev, rtsta_pos, rtsta);
+}
+
+/**
+ * pcie_pme_walk_bus - Scan a PCI bus for devices asserting PME#.
+ * @bus: PCI bus to scan.
+ *
+ * Scan given PCI bus and all buses under it for devices asserting PME#.
+ */
+static bool pcie_pme_walk_bus(struct pci_bus *bus)
+{
+	struct pci_dev *dev;
+	bool ret = false;
+
+	list_for_each_entry(dev, &bus->devices, bus_list) {
+		/* Skip PCIe devices in case we started from a root port. */
+		if (!dev->is_pcie && pci_check_pme_status(dev)) {
+			pm_request_resume(&dev->dev);
+			ret = true;
+		}
+
+		if (dev->subordinate && pcie_pme_walk_bus(dev->subordinate))
+			ret = true;
+	}
+
+	return ret;
+}
+
+/**
+ * pcie_pme_from_pci_bridge - Check if PCIe-PCI bridge generated a PME.
+ * @bus: Secondary bus of the bridge.
+ * @devfn: Device/function number to check.
+ *
+ * PME from PCI devices under a PCIe-PCI bridge may be converted to an in-band
+ * PCIe PME message.  In such that case the bridge should use the Requester ID
+ * of device/function number 0 on its secondary bus.
+ */
+static bool pcie_pme_from_pci_bridge(struct pci_bus *bus, u8 devfn)
+{
+	struct pci_dev *dev;
+	bool found = false;
+
+	if (devfn)
+		return false;
+
+	dev = pci_dev_get(bus->self);
+	if (!dev)
+		return false;
+
+	if (dev->is_pcie && dev->pcie_type == PCI_EXP_TYPE_PCI_BRIDGE) {
+		down_read(&pci_bus_sem);
+		if (pcie_pme_walk_bus(bus))
+			found = true;
+		up_read(&pci_bus_sem);
+	}
+
+	pci_dev_put(dev);
+	return found;
+}
+
+/**
+ * pcie_pme_handle_request - Find device that generated PME and handle it.
+ * @port: Root port or event collector that generated the PME interrupt.
+ * @req_id: PCIe Requester ID of the device that generated the PME.
+ */
+static void pcie_pme_handle_request(struct pci_dev *port, u16 req_id)
+{
+	u8 busnr = req_id >> 8, devfn = req_id & 0xff;
+	struct pci_bus *bus;
+	struct pci_dev *dev;
+	bool found = false;
+
+	/* First, check if the PME is from the root port itself. */
+	if (port->devfn == devfn && port->bus->number == busnr) {
+		if (pci_check_pme_status(port)) {
+			pm_request_resume(&port->dev);
+			found = true;
+		} else {
+			/*
+			 * Apparently, the root port generated the PME on behalf
+			 * of a non-PCIe device downstream.  If this is done by
+			 * a root port, the Requester ID field in its status
+			 * register may contain either the root port's, or the
+			 * source device's information (PCI Express Base
+			 * Specification, Rev. 2.0, Section 6.1.9).
+			 */
+			down_read(&pci_bus_sem);
+			found = pcie_pme_walk_bus(port->subordinate);
+			up_read(&pci_bus_sem);
+		}
+		goto out;
+	}
+
+	/* Second, find the bus the source device is on. */
+	bus = pci_find_bus(pci_domain_nr(port->bus), busnr);
+	if (!bus)
+		goto out;
+
+	/* Next, check if the PME is from a PCIe-PCI bridge. */
+	found = pcie_pme_from_pci_bridge(bus, devfn);
+	if (found)
+		goto out;
+
+	/* Finally, try to find the PME source on the bus. */
+	down_read(&pci_bus_sem);
+	list_for_each_entry(dev, &bus->devices, bus_list) {
+		pci_dev_get(dev);
+		if (dev->devfn == devfn) {
+			found = true;
+			break;
+		}
+		pci_dev_put(dev);
+	}
+	up_read(&pci_bus_sem);
+
+	if (found) {
+		/* The device is there, but we have to check its PME status. */
+		found = pci_check_pme_status(dev);
+		if (found)
+			pm_request_resume(&dev->dev);
+		pci_dev_put(dev);
+	} else if (devfn) {
+		/*
+		 * The device is not there, but we can still try to recover by
+		 * assuming that the PME was reported by a PCIe-PCI bridge that
+		 * used devfn different from zero.
+		 */
+		dev_dbg(&port->dev, "PME interrupt generated for "
+			"non-existent device %02x:%02x.%d\n",
+			busnr, PCI_SLOT(devfn), PCI_FUNC(devfn));
+		found = pcie_pme_from_pci_bridge(bus, 0);
+	}
+
+ out:
+	if (!found)
+		dev_dbg(&port->dev, "Spurious native PME interrupt!\n");
+}
+
+/**
+ * pcie_pme_work_fn - Work handler for PCIe PME interrupt.
+ * @work: Work structure giving access to service data.
+ */
+static void pcie_pme_work_fn(struct work_struct *work)
+{
+	struct pcie_pme_service_data *data =
+			container_of(work, struct pcie_pme_service_data, work);
+	struct pci_dev *port = data->srv->port;
+	int rtsta_pos;
+	u32 rtsta;
+
+	rtsta_pos = pci_find_capability(port, PCI_CAP_ID_EXP) + PCI_EXP_RTSTA;
+
+	spin_lock_irq(&data->lock);
+
+	for (;;) {
+		if (data->noirq)
+			break;
+
+		pci_read_config_dword(port, rtsta_pos, &rtsta);
+		if (rtsta & PCI_EXP_RTSTA_PME) {
+			/*
+			 * Clear PME status of the port.  If there are other
+			 * pending PMEs, the status will be set again.
+			 */
+			pcie_pme_clear_status(port);
+
+			spin_unlock_irq(&data->lock);
+			pcie_pme_handle_request(port, rtsta & 0xffff);
+			spin_lock_irq(&data->lock);
+
+			continue;
+		}
+
+		/* No need to loop if there are no more PMEs pending. */
+		if (!(rtsta & PCI_EXP_RTSTA_PENDING))
+			break;
+
+		spin_unlock_irq(&data->lock);
+		cpu_relax();
+		spin_lock_irq(&data->lock);
+	}
+
+	if (!data->noirq)
+		pcie_pme_interrupt_enable(port, true);
+
+	spin_unlock_irq(&data->lock);
+}
+
+/**
+ * pcie_pme_irq - Interrupt handler for PCIe root port PME interrupt.
+ * @irq: Interrupt vector.
+ * @context: Interrupt context pointer.
+ */
+static irqreturn_t pcie_pme_irq(int irq, void *context)
+{
+	struct pci_dev *port;
+	struct pcie_pme_service_data *data;
+	int rtsta_pos;
+	u32 rtsta;
+	unsigned long flags;
+
+	port = ((struct pcie_device *)context)->port;
+	data = get_service_data((struct pcie_device *)context);
+
+	rtsta_pos = pci_find_capability(port, PCI_CAP_ID_EXP) + PCI_EXP_RTSTA;
+
+	spin_lock_irqsave(&data->lock, flags);
+	pci_read_config_dword(port, rtsta_pos, &rtsta);
+
+	if (!(rtsta & PCI_EXP_RTSTA_PME)) {
+		spin_unlock_irqrestore(&data->lock, flags);
+		return IRQ_NONE;
+	}
+
+	pcie_pme_interrupt_enable(port, false);
+	spin_unlock_irqrestore(&data->lock, flags);
+
+	/* We don't use pm_wq, because it's freezable. */
+	schedule_work(&data->work);
+
+	return IRQ_HANDLED;
+}
+
+/**
+ * pcie_pme_set_native - Set the PME interrupt flag for given device.
+ * @dev: PCI device to handle.
+ * @ign: Ignored.
+ */
+static int pcie_pme_set_native(struct pci_dev *dev, void *ign)
+{
+	dev_info(&dev->dev, "Signaling PME through PCIe PME interrupt\n");
+
+	device_set_run_wake(&dev->dev, true);
+	dev->pme_interrupt = true;
+	return 0;
+}
+
+/**
+ * pcie_pme_mark_devices - Set the PME interrupt flag for devices below a port.
+ * @port: PCIe root port or event collector to handle.
+ *
+ * For each device below given root port, including the port itself (or for each
+ * root complex integrated endpoint if @port is a root complex event collector)
+ * set the flag indicating that it can signal run-time wake-up events via PCIe
+ * PME interrupts.
+ */
+static void pcie_pme_mark_devices(struct pci_dev *port)
+{
+	pcie_pme_set_native(port, NULL);
+	if (port->subordinate) {
+		pci_walk_bus(port->subordinate, pcie_pme_set_native, NULL);
+	} else {
+		struct pci_bus *bus = port->bus;
+		struct pci_dev *dev;
+
+		/* Check if this is a root port event collector. */
+		if (port->pcie_type != PCI_EXP_TYPE_RC_EC || !bus)
+			return;
+
+		down_read(&pci_bus_sem);
+		list_for_each_entry(dev, &bus->devices, bus_list)
+			if (dev->is_pcie
+			    && dev->pcie_type == PCI_EXP_TYPE_RC_END)
+				pcie_pme_set_native(dev, NULL);
+		up_read(&pci_bus_sem);
+	}
+}
+
+/**
+ * pcie_pme_probe - Initialize PCIe PME service for given root port.
+ * @srv: PCIe service to initialize.
+ */
+static int pcie_pme_probe(struct pcie_device *srv)
+{
+	struct pci_dev *port;
+	struct pcie_pme_service_data *data;
+	int ret;
+
+	if (!pcie_pme_platform_setup(srv))
+		return -EACCES;
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	spin_lock_init(&data->lock);
+	INIT_WORK(&data->work, pcie_pme_work_fn);
+	data->srv = srv;
+	set_service_data(srv, data);
+
+	port = srv->port;
+	pcie_pme_interrupt_enable(port, false);
+	pcie_pme_clear_status(port);
+
+	ret = request_irq(srv->irq, pcie_pme_irq, IRQF_SHARED, "PCIe PME", srv);
+	if (ret) {
+		kfree(data);
+	} else {
+		pcie_pme_mark_devices(port);
+		pcie_pme_interrupt_enable(port, true);
+	}
+
+	return ret;
+}
+
+/**
+ * pcie_pme_suspend - Suspend PCIe PME service device.
+ * @srv: PCIe service device to suspend.
+ */
+static int pcie_pme_suspend(struct pcie_device *srv)
+{
+	struct pcie_pme_service_data *data = get_service_data(srv);
+	struct pci_dev *port = srv->port;
+
+	spin_lock_irq(&data->lock);
+	pcie_pme_interrupt_enable(port, false);
+	pcie_pme_clear_status(port);
+	data->noirq = true;
+	spin_unlock_irq(&data->lock);
+
+	synchronize_irq(srv->irq);
+
+	return 0;
+}
+
+/**
+ * pcie_pme_resume - Resume PCIe PME service device.
+ * @srv - PCIe service device to resume.
+ */
+static int pcie_pme_resume(struct pcie_device *srv)
+{
+	struct pcie_pme_service_data *data = get_service_data(srv);
+	struct pci_dev *port = srv->port;
+
+	spin_lock_irq(&data->lock);
+	data->noirq = false;
+	pcie_pme_clear_status(port);
+	pcie_pme_interrupt_enable(port, true);
+	spin_unlock_irq(&data->lock);
+
+	return 0;
+}
+
+/**
+ * pcie_pme_remove - Prepare PCIe PME service device for removal.
+ * @srv - PCIe service device to resume.
+ */
+static void pcie_pme_remove(struct pcie_device *srv)
+{
+	pcie_pme_suspend(srv);
+	free_irq(srv->irq, srv);
+	kfree(get_service_data(srv));
+}
+
+static struct pcie_port_service_driver pcie_pme_driver = {
+	.name		= "pcie_pme",
+	.port_type 	= PCI_EXP_TYPE_ROOT_PORT,
+	.service 	= PCIE_PORT_SERVICE_PME,
+
+	.probe		= pcie_pme_probe,
+	.suspend	= pcie_pme_suspend,
+	.resume		= pcie_pme_resume,
+	.remove		= pcie_pme_remove,
+};
+
+/**
+ * pcie_pme_service_init - Register the PCIe PME service driver.
+ */
+static int __init pcie_pme_service_init(void)
+{
+	return pcie_pme_disabled ?
+		-ENODEV : pcie_port_service_register(&pcie_pme_driver);
+}
+
+module_init(pcie_pme_service_init);
Index: linux-2.6/drivers/pci/pcie/pme/pcie_pme_acpi.c
===================================================================
--- /dev/null
+++ linux-2.6/drivers/pci/pcie/pme/pcie_pme_acpi.c
@@ -0,0 +1,54 @@
+/*
+ * PCIe Native PME support, ACPI-related part
+ *
+ * Copyright (C) 2009 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc.
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License V2.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ */
+
+#include <linux/pci.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/acpi.h>
+#include <linux/pci-acpi.h>
+#include <linux/pcieport_if.h>
+
+/**
+ * pcie_pme_acpi_setup - Request the ACPI BIOS to release control over PCIe PME.
+ * @srv - PCIe PME service for a root port or event collector.
+ *
+ * Invoked when the PCIe bus type loads PCIe PME service driver.  To avoid
+ * conflict with the BIOS PCIe support requires the BIOS to yield PCIe PME
+ * control to the kernel.
+ */
+int pcie_pme_acpi_setup(struct pcie_device *srv)
+{
+	acpi_status status = AE_NOT_FOUND;
+	struct pci_dev *port = srv->port;
+	acpi_handle handle;
+	int error = 0;
+
+	if (acpi_pci_disabled)
+		return -ENOSYS;
+
+	dev_info(&port->dev, "Requesting control of PCIe PME from ACPI BIOS\n");
+
+	handle = acpi_find_root_bridge_handle(port);
+	if (!handle)
+		return -EINVAL;
+
+	status = acpi_pci_osc_control_set(handle,
+			OSC_PCI_EXPRESS_PME_CONTROL |
+			OSC_PCI_EXPRESS_CAP_STRUCTURE_CONTROL);
+	if (ACPI_FAILURE(status)) {
+		dev_info(&port->dev,
+			"Failed to receive control of PCIe PME service: %s\n",
+			(status == AE_SUPPORT || status == AE_NOT_FOUND) ?
+			"no _OSC support" : "ACPI _OSC failed");
+		error = -ENODEV;
+	}
+
+	return error;
+}
Index: linux-2.6/drivers/pci/pcie/pme/Makefile
===================================================================
--- /dev/null
+++ linux-2.6/drivers/pci/pcie/pme/Makefile
@@ -0,0 +1,8 @@
+#
+# Makefile for PCI-Express Root Port PME signaling driver
+#
+
+obj-$(CONFIG_PCIE_PME) += pmedriver.o
+
+pmedriver-objs := pcie_pme.o
+pmedriver-$(CONFIG_ACPI) += pcie_pme_acpi.o
Index: linux-2.6/drivers/pci/pcie/pme/pcie_pme.h
===================================================================
--- /dev/null
+++ linux-2.6/drivers/pci/pcie/pme/pcie_pme.h
@@ -0,0 +1,28 @@
+/*
+ * drivers/pci/pcie/pme/pcie_pme.h
+ *
+ * PCI Express Root Port PME signaling support
+ *
+ * Copyright (C) 2009 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc.
+ */
+
+#ifndef _PCIE_PME_H_
+#define _PCIE_PME_H_
+
+struct pcie_device;
+
+#ifdef CONFIG_ACPI
+extern int pcie_pme_acpi_setup(struct pcie_device *srv);
+
+static inline int pcie_pme_platform_notify(struct pcie_device *srv)
+{
+	return pcie_pme_acpi_setup(srv);
+}
+#else /* !CONFIG_ACPI */
+static inline int pcie_pme_platform_notify(struct pcie_device *srv)
+{
+	return 0;
+}
+#endif /* !CONFIG_ACPI */
+
+#endif
Index: linux-2.6/include/linux/pci.h
===================================================================
--- linux-2.6.orig/include/linux/pci.h
+++ linux-2.6/include/linux/pci.h
@@ -239,6 +239,7 @@ struct pci_dev {
 					   configuration space */
 	unsigned int	pme_support:5;	/* Bitmask of states from which PME#
 					   can be generated */
+	unsigned int	pme_interrupt:1;
 	unsigned int	d1_support:1;	/* Low power state D1 is supported */
 	unsigned int	d2_support:1;	/* Low power state D2 is supported */
 	unsigned int	no_d1d2:1;	/* Only allow D0 and D3 */
Index: linux-2.6/Documentation/kernel-parameters.txt
===================================================================
--- linux-2.6.orig/Documentation/kernel-parameters.txt
+++ linux-2.6/Documentation/kernel-parameters.txt
@@ -1986,6 +1986,12 @@ and is between 256 and 4096 characters. 
 		force	Enable ASPM even on devices that claim not to support it.
 			WARNING: Forcing ASPM on may cause system lockups.
 
+	pcie_pme=	[PCIE,PM] Native PCIe PME signaling options:
+		off	Do not use native PCIe PME signaling.
+		force	Use native PCIe PME signaling even if the BIOS refuses
+			to allow the kernel to control the relevant PCIe config
+			registers.
+
 	pcmv=		[HW,PCMCIA] BadgePAD 4
 
 	pd.		[PARIDE]


^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH 3/12] PCI PM: PCIe PME root port service driver (rev. 5)
  2009-12-27 19:57 [PATCH 0/12] PCI run-time PM support (rev. 2) Rafael J. Wysocki
                   ` (2 preceding siblings ...)
  2009-12-27 20:00 ` Rafael J. Wysocki
@ 2009-12-27 20:01 ` Rafael J. Wysocki
  2009-12-27 20:01 ` Rafael J. Wysocki
                   ` (18 subsequent siblings)
  22 siblings, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2009-12-27 20:01 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Linux PCI, LKML, ACPI Devel Maling List, Francois Romieu,
	pm list, Matthew Garrett

From: Rafael J. Wysocki <rjw@sisk.pl>

PCIe native PME detection mechanism is based on interrupts generated
by root ports or event collectors every time a PCIe device sends a
PME message upstream.

Once a PME message has been sent by an endpoint device and received
by its root port (or event collector in the case of root complex
integrated endpoints), the Requester ID from the message header is
registered in the root port's Root Status register.  At the same
time, the PME Status bit of the Root Status register is set to
indicate that there's a PME to handle.  If PCIe PME interrupt is
enabled for the root port, it generates an interrupt once the PME
Status has been set.  After receiving the interrupt, the kernel can
identify the PCIe device that generated the PME using the Requester
ID from the root port's Root Status register. [For details, see PCI
Express Base Specification, Rev. 2.0.]

Implement a driver for the PCIe PME root port service working in
accordance with the above description.

Based on a patch from Shaohua Li <shaohua.li@intel.com>.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
 Documentation/kernel-parameters.txt  |    6 
 drivers/pci/pcie/Kconfig             |    4 
 drivers/pci/pcie/Makefile            |    2 
 drivers/pci/pcie/pme/Makefile        |    8 
 drivers/pci/pcie/pme/pcie_pme.c      |  493 +++++++++++++++++++++++++++++++++++
 drivers/pci/pcie/pme/pcie_pme.h      |   28 +
 drivers/pci/pcie/pme/pcie_pme_acpi.c |   54 +++
 include/linux/pci.h                  |    1 
 8 files changed, 596 insertions(+)

Index: linux-2.6/drivers/pci/pcie/Kconfig
===================================================================
--- linux-2.6.orig/drivers/pci/pcie/Kconfig
+++ linux-2.6/drivers/pci/pcie/Kconfig
@@ -46,3 +46,7 @@ config PCIEASPM_DEBUG
 	help
 	  This enables PCI Express ASPM debug support. It will add per-device
 	  interface to control ASPM.
+
+config PCIE_PME
+	def_bool y
+	depends on PCIEPORTBUS && PM_RUNTIME && EXPERIMENTAL
Index: linux-2.6/drivers/pci/pcie/Makefile
===================================================================
--- linux-2.6.orig/drivers/pci/pcie/Makefile
+++ linux-2.6/drivers/pci/pcie/Makefile
@@ -11,3 +11,5 @@ obj-$(CONFIG_PCIEPORTBUS)	+= pcieportdrv
 
 # Build PCI Express AER if needed
 obj-$(CONFIG_PCIEAER)		+= aer/
+
+obj-$(CONFIG_PCIE_PME) += pme/
Index: linux-2.6/drivers/pci/pcie/pme/pcie_pme.c
===================================================================
--- /dev/null
+++ linux-2.6/drivers/pci/pcie/pme/pcie_pme.c
@@ -0,0 +1,493 @@
+/*
+ * PCIe Native PME support
+ *
+ * Copyright (C) 2007 - 2009 Intel Corp
+ * Copyright (C) 2007 - 2009 Shaohua Li <shaohua.li@intel.com>
+ * Copyright (C) 2009 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc.
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License V2.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/pcieport_if.h>
+#include <linux/acpi.h>
+#include <linux/pci-acpi.h>
+#include <linux/pm_runtime.h>
+
+#include "../../pci.h"
+#include "pcie_pme.h"
+
+#define PCI_EXP_RTSTA_PME	0x10000 /* PME status */
+#define PCI_EXP_RTSTA_PENDING	0x20000 /* PME pending */
+
+/*
+ * If set, this switch will prevent the PCIe root port PME service driver from
+ * being registered.  Consequently, the interrupt-based PCIe PME signaling will
+ * not be used by any PCIe root ports in that case.
+ */
+static bool pcie_pme_disabled;
+
+/*
+ * The PCI Express Base Specification 2.0, Section 6.1.8, states the following:
+ * "In order to maintain compatibility with non-PCI Express-aware system
+ * software, system power management logic must be configured by firmware to use
+ * the legacy mechanism of signaling PME by default.  PCI Express-aware system
+ * software must notify the firmware prior to enabling native, interrupt-based
+ * PME signaling."  However, if the platform doesn't provide us with a suitable
+ * notification mechanism or the notification fails, it is not clear whether or
+ * not we are supposed to use the interrupt-based PCIe PME signaling.  The
+ * switch below can be used to indicate the desired behaviour.  When set, it
+ * will make the kernel use the interrupt-based PCIe PME signaling regardless of
+ * the platform notification status, although the kernel will attempt to notify
+ * the platform anyway.  When unset, it will prevent the kernel from using the
+ * the interrupt-based PCIe PME signaling if the platform notification fails,
+ * which is the default.
+ */
+static bool pcie_pme_force_enable;
+
+static int __init pcie_pme_setup(char *str)
+{
+	if (!strcmp(str, "off"))
+		pcie_pme_disabled = true;
+	else if (!strcmp(str, "force"))
+		pcie_pme_force_enable = true;
+	return 1;
+}
+__setup("pcie_pme=", pcie_pme_setup);
+
+/**
+ * pcie_pme_platform_setup - Ensure that the kernel controls the PCIe PME.
+ * @srv: PCIe PME root port service to use for carrying out the check.
+ *
+ * Notify the platform that the native PCIe PME is going to be used and return
+ * 'true' if the control of the PCIe PME registers has been acquired from the
+ * platform.
+ */
+static bool pcie_pme_platform_setup(struct pcie_device *srv)
+{
+	return !pcie_pme_platform_notify(srv) || pcie_pme_force_enable;
+}
+
+struct pcie_pme_service_data {
+	spinlock_t lock;
+	struct pcie_device *srv;
+	struct work_struct work;
+	bool noirq; /* Don't enable the PME interrupt used by this service. */
+};
+
+/**
+ * pcie_pme_interrupt_enable - Enable/disable PCIe PME interrupt generation.
+ * @dev: PCIe root port or event collector.
+ * @enable: Enable or disable the interrupt.
+ */
+static void pcie_pme_interrupt_enable(struct pci_dev *dev, bool enable)
+{
+	int rtctl_pos;
+	u16 rtctl;
+
+	rtctl_pos = pci_find_capability(dev, PCI_CAP_ID_EXP) + PCI_EXP_RTCTL;
+
+	pci_read_config_word(dev, rtctl_pos, &rtctl);
+	if (enable)
+		rtctl |= PCI_EXP_RTCTL_PMEIE;
+	else
+		rtctl &= ~PCI_EXP_RTCTL_PMEIE;
+	pci_write_config_word(dev, rtctl_pos, rtctl);
+}
+
+/**
+ * pcie_pme_clear_status - Clear root port PME interrupt status.
+ * @dev: PCIe root port or event collector.
+ */
+static void pcie_pme_clear_status(struct pci_dev *dev)
+{
+	int rtsta_pos;
+	u32 rtsta;
+
+	rtsta_pos = pci_find_capability(dev, PCI_CAP_ID_EXP) + PCI_EXP_RTSTA;
+
+	pci_read_config_dword(dev, rtsta_pos, &rtsta);
+	rtsta |= PCI_EXP_RTSTA_PME;
+	pci_write_config_dword(dev, rtsta_pos, rtsta);
+}
+
+/**
+ * pcie_pme_walk_bus - Scan a PCI bus for devices asserting PME#.
+ * @bus: PCI bus to scan.
+ *
+ * Scan given PCI bus and all buses under it for devices asserting PME#.
+ */
+static bool pcie_pme_walk_bus(struct pci_bus *bus)
+{
+	struct pci_dev *dev;
+	bool ret = false;
+
+	list_for_each_entry(dev, &bus->devices, bus_list) {
+		/* Skip PCIe devices in case we started from a root port. */
+		if (!dev->is_pcie && pci_check_pme_status(dev)) {
+			pm_request_resume(&dev->dev);
+			ret = true;
+		}
+
+		if (dev->subordinate && pcie_pme_walk_bus(dev->subordinate))
+			ret = true;
+	}
+
+	return ret;
+}
+
+/**
+ * pcie_pme_from_pci_bridge - Check if PCIe-PCI bridge generated a PME.
+ * @bus: Secondary bus of the bridge.
+ * @devfn: Device/function number to check.
+ *
+ * PME from PCI devices under a PCIe-PCI bridge may be converted to an in-band
+ * PCIe PME message.  In such that case the bridge should use the Requester ID
+ * of device/function number 0 on its secondary bus.
+ */
+static bool pcie_pme_from_pci_bridge(struct pci_bus *bus, u8 devfn)
+{
+	struct pci_dev *dev;
+	bool found = false;
+
+	if (devfn)
+		return false;
+
+	dev = pci_dev_get(bus->self);
+	if (!dev)
+		return false;
+
+	if (dev->is_pcie && dev->pcie_type == PCI_EXP_TYPE_PCI_BRIDGE) {
+		down_read(&pci_bus_sem);
+		if (pcie_pme_walk_bus(bus))
+			found = true;
+		up_read(&pci_bus_sem);
+	}
+
+	pci_dev_put(dev);
+	return found;
+}
+
+/**
+ * pcie_pme_handle_request - Find device that generated PME and handle it.
+ * @port: Root port or event collector that generated the PME interrupt.
+ * @req_id: PCIe Requester ID of the device that generated the PME.
+ */
+static void pcie_pme_handle_request(struct pci_dev *port, u16 req_id)
+{
+	u8 busnr = req_id >> 8, devfn = req_id & 0xff;
+	struct pci_bus *bus;
+	struct pci_dev *dev;
+	bool found = false;
+
+	/* First, check if the PME is from the root port itself. */
+	if (port->devfn == devfn && port->bus->number == busnr) {
+		if (pci_check_pme_status(port)) {
+			pm_request_resume(&port->dev);
+			found = true;
+		} else {
+			/*
+			 * Apparently, the root port generated the PME on behalf
+			 * of a non-PCIe device downstream.  If this is done by
+			 * a root port, the Requester ID field in its status
+			 * register may contain either the root port's, or the
+			 * source device's information (PCI Express Base
+			 * Specification, Rev. 2.0, Section 6.1.9).
+			 */
+			down_read(&pci_bus_sem);
+			found = pcie_pme_walk_bus(port->subordinate);
+			up_read(&pci_bus_sem);
+		}
+		goto out;
+	}
+
+	/* Second, find the bus the source device is on. */
+	bus = pci_find_bus(pci_domain_nr(port->bus), busnr);
+	if (!bus)
+		goto out;
+
+	/* Next, check if the PME is from a PCIe-PCI bridge. */
+	found = pcie_pme_from_pci_bridge(bus, devfn);
+	if (found)
+		goto out;
+
+	/* Finally, try to find the PME source on the bus. */
+	down_read(&pci_bus_sem);
+	list_for_each_entry(dev, &bus->devices, bus_list) {
+		pci_dev_get(dev);
+		if (dev->devfn == devfn) {
+			found = true;
+			break;
+		}
+		pci_dev_put(dev);
+	}
+	up_read(&pci_bus_sem);
+
+	if (found) {
+		/* The device is there, but we have to check its PME status. */
+		found = pci_check_pme_status(dev);
+		if (found)
+			pm_request_resume(&dev->dev);
+		pci_dev_put(dev);
+	} else if (devfn) {
+		/*
+		 * The device is not there, but we can still try to recover by
+		 * assuming that the PME was reported by a PCIe-PCI bridge that
+		 * used devfn different from zero.
+		 */
+		dev_dbg(&port->dev, "PME interrupt generated for "
+			"non-existent device %02x:%02x.%d\n",
+			busnr, PCI_SLOT(devfn), PCI_FUNC(devfn));
+		found = pcie_pme_from_pci_bridge(bus, 0);
+	}
+
+ out:
+	if (!found)
+		dev_dbg(&port->dev, "Spurious native PME interrupt!\n");
+}
+
+/**
+ * pcie_pme_work_fn - Work handler for PCIe PME interrupt.
+ * @work: Work structure giving access to service data.
+ */
+static void pcie_pme_work_fn(struct work_struct *work)
+{
+	struct pcie_pme_service_data *data =
+			container_of(work, struct pcie_pme_service_data, work);
+	struct pci_dev *port = data->srv->port;
+	int rtsta_pos;
+	u32 rtsta;
+
+	rtsta_pos = pci_find_capability(port, PCI_CAP_ID_EXP) + PCI_EXP_RTSTA;
+
+	spin_lock_irq(&data->lock);
+
+	for (;;) {
+		if (data->noirq)
+			break;
+
+		pci_read_config_dword(port, rtsta_pos, &rtsta);
+		if (rtsta & PCI_EXP_RTSTA_PME) {
+			/*
+			 * Clear PME status of the port.  If there are other
+			 * pending PMEs, the status will be set again.
+			 */
+			pcie_pme_clear_status(port);
+
+			spin_unlock_irq(&data->lock);
+			pcie_pme_handle_request(port, rtsta & 0xffff);
+			spin_lock_irq(&data->lock);
+
+			continue;
+		}
+
+		/* No need to loop if there are no more PMEs pending. */
+		if (!(rtsta & PCI_EXP_RTSTA_PENDING))
+			break;
+
+		spin_unlock_irq(&data->lock);
+		cpu_relax();
+		spin_lock_irq(&data->lock);
+	}
+
+	if (!data->noirq)
+		pcie_pme_interrupt_enable(port, true);
+
+	spin_unlock_irq(&data->lock);
+}
+
+/**
+ * pcie_pme_irq - Interrupt handler for PCIe root port PME interrupt.
+ * @irq: Interrupt vector.
+ * @context: Interrupt context pointer.
+ */
+static irqreturn_t pcie_pme_irq(int irq, void *context)
+{
+	struct pci_dev *port;
+	struct pcie_pme_service_data *data;
+	int rtsta_pos;
+	u32 rtsta;
+	unsigned long flags;
+
+	port = ((struct pcie_device *)context)->port;
+	data = get_service_data((struct pcie_device *)context);
+
+	rtsta_pos = pci_find_capability(port, PCI_CAP_ID_EXP) + PCI_EXP_RTSTA;
+
+	spin_lock_irqsave(&data->lock, flags);
+	pci_read_config_dword(port, rtsta_pos, &rtsta);
+
+	if (!(rtsta & PCI_EXP_RTSTA_PME)) {
+		spin_unlock_irqrestore(&data->lock, flags);
+		return IRQ_NONE;
+	}
+
+	pcie_pme_interrupt_enable(port, false);
+	spin_unlock_irqrestore(&data->lock, flags);
+
+	/* We don't use pm_wq, because it's freezable. */
+	schedule_work(&data->work);
+
+	return IRQ_HANDLED;
+}
+
+/**
+ * pcie_pme_set_native - Set the PME interrupt flag for given device.
+ * @dev: PCI device to handle.
+ * @ign: Ignored.
+ */
+static int pcie_pme_set_native(struct pci_dev *dev, void *ign)
+{
+	dev_info(&dev->dev, "Signaling PME through PCIe PME interrupt\n");
+
+	device_set_run_wake(&dev->dev, true);
+	dev->pme_interrupt = true;
+	return 0;
+}
+
+/**
+ * pcie_pme_mark_devices - Set the PME interrupt flag for devices below a port.
+ * @port: PCIe root port or event collector to handle.
+ *
+ * For each device below given root port, including the port itself (or for each
+ * root complex integrated endpoint if @port is a root complex event collector)
+ * set the flag indicating that it can signal run-time wake-up events via PCIe
+ * PME interrupts.
+ */
+static void pcie_pme_mark_devices(struct pci_dev *port)
+{
+	pcie_pme_set_native(port, NULL);
+	if (port->subordinate) {
+		pci_walk_bus(port->subordinate, pcie_pme_set_native, NULL);
+	} else {
+		struct pci_bus *bus = port->bus;
+		struct pci_dev *dev;
+
+		/* Check if this is a root port event collector. */
+		if (port->pcie_type != PCI_EXP_TYPE_RC_EC || !bus)
+			return;
+
+		down_read(&pci_bus_sem);
+		list_for_each_entry(dev, &bus->devices, bus_list)
+			if (dev->is_pcie
+			    && dev->pcie_type == PCI_EXP_TYPE_RC_END)
+				pcie_pme_set_native(dev, NULL);
+		up_read(&pci_bus_sem);
+	}
+}
+
+/**
+ * pcie_pme_probe - Initialize PCIe PME service for given root port.
+ * @srv: PCIe service to initialize.
+ */
+static int pcie_pme_probe(struct pcie_device *srv)
+{
+	struct pci_dev *port;
+	struct pcie_pme_service_data *data;
+	int ret;
+
+	if (!pcie_pme_platform_setup(srv))
+		return -EACCES;
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	spin_lock_init(&data->lock);
+	INIT_WORK(&data->work, pcie_pme_work_fn);
+	data->srv = srv;
+	set_service_data(srv, data);
+
+	port = srv->port;
+	pcie_pme_interrupt_enable(port, false);
+	pcie_pme_clear_status(port);
+
+	ret = request_irq(srv->irq, pcie_pme_irq, IRQF_SHARED, "PCIe PME", srv);
+	if (ret) {
+		kfree(data);
+	} else {
+		pcie_pme_mark_devices(port);
+		pcie_pme_interrupt_enable(port, true);
+	}
+
+	return ret;
+}
+
+/**
+ * pcie_pme_suspend - Suspend PCIe PME service device.
+ * @srv: PCIe service device to suspend.
+ */
+static int pcie_pme_suspend(struct pcie_device *srv)
+{
+	struct pcie_pme_service_data *data = get_service_data(srv);
+	struct pci_dev *port = srv->port;
+
+	spin_lock_irq(&data->lock);
+	pcie_pme_interrupt_enable(port, false);
+	pcie_pme_clear_status(port);
+	data->noirq = true;
+	spin_unlock_irq(&data->lock);
+
+	synchronize_irq(srv->irq);
+
+	return 0;
+}
+
+/**
+ * pcie_pme_resume - Resume PCIe PME service device.
+ * @srv - PCIe service device to resume.
+ */
+static int pcie_pme_resume(struct pcie_device *srv)
+{
+	struct pcie_pme_service_data *data = get_service_data(srv);
+	struct pci_dev *port = srv->port;
+
+	spin_lock_irq(&data->lock);
+	data->noirq = false;
+	pcie_pme_clear_status(port);
+	pcie_pme_interrupt_enable(port, true);
+	spin_unlock_irq(&data->lock);
+
+	return 0;
+}
+
+/**
+ * pcie_pme_remove - Prepare PCIe PME service device for removal.
+ * @srv - PCIe service device to resume.
+ */
+static void pcie_pme_remove(struct pcie_device *srv)
+{
+	pcie_pme_suspend(srv);
+	free_irq(srv->irq, srv);
+	kfree(get_service_data(srv));
+}
+
+static struct pcie_port_service_driver pcie_pme_driver = {
+	.name		= "pcie_pme",
+	.port_type 	= PCI_EXP_TYPE_ROOT_PORT,
+	.service 	= PCIE_PORT_SERVICE_PME,
+
+	.probe		= pcie_pme_probe,
+	.suspend	= pcie_pme_suspend,
+	.resume		= pcie_pme_resume,
+	.remove		= pcie_pme_remove,
+};
+
+/**
+ * pcie_pme_service_init - Register the PCIe PME service driver.
+ */
+static int __init pcie_pme_service_init(void)
+{
+	return pcie_pme_disabled ?
+		-ENODEV : pcie_port_service_register(&pcie_pme_driver);
+}
+
+module_init(pcie_pme_service_init);
Index: linux-2.6/drivers/pci/pcie/pme/pcie_pme_acpi.c
===================================================================
--- /dev/null
+++ linux-2.6/drivers/pci/pcie/pme/pcie_pme_acpi.c
@@ -0,0 +1,54 @@
+/*
+ * PCIe Native PME support, ACPI-related part
+ *
+ * Copyright (C) 2009 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc.
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License V2.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ */
+
+#include <linux/pci.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/acpi.h>
+#include <linux/pci-acpi.h>
+#include <linux/pcieport_if.h>
+
+/**
+ * pcie_pme_acpi_setup - Request the ACPI BIOS to release control over PCIe PME.
+ * @srv - PCIe PME service for a root port or event collector.
+ *
+ * Invoked when the PCIe bus type loads PCIe PME service driver.  To avoid
+ * conflict with the BIOS PCIe support requires the BIOS to yield PCIe PME
+ * control to the kernel.
+ */
+int pcie_pme_acpi_setup(struct pcie_device *srv)
+{
+	acpi_status status = AE_NOT_FOUND;
+	struct pci_dev *port = srv->port;
+	acpi_handle handle;
+	int error = 0;
+
+	if (acpi_pci_disabled)
+		return -ENOSYS;
+
+	dev_info(&port->dev, "Requesting control of PCIe PME from ACPI BIOS\n");
+
+	handle = acpi_find_root_bridge_handle(port);
+	if (!handle)
+		return -EINVAL;
+
+	status = acpi_pci_osc_control_set(handle,
+			OSC_PCI_EXPRESS_PME_CONTROL |
+			OSC_PCI_EXPRESS_CAP_STRUCTURE_CONTROL);
+	if (ACPI_FAILURE(status)) {
+		dev_info(&port->dev,
+			"Failed to receive control of PCIe PME service: %s\n",
+			(status == AE_SUPPORT || status == AE_NOT_FOUND) ?
+			"no _OSC support" : "ACPI _OSC failed");
+		error = -ENODEV;
+	}
+
+	return error;
+}
Index: linux-2.6/drivers/pci/pcie/pme/Makefile
===================================================================
--- /dev/null
+++ linux-2.6/drivers/pci/pcie/pme/Makefile
@@ -0,0 +1,8 @@
+#
+# Makefile for PCI-Express Root Port PME signaling driver
+#
+
+obj-$(CONFIG_PCIE_PME) += pmedriver.o
+
+pmedriver-objs := pcie_pme.o
+pmedriver-$(CONFIG_ACPI) += pcie_pme_acpi.o
Index: linux-2.6/drivers/pci/pcie/pme/pcie_pme.h
===================================================================
--- /dev/null
+++ linux-2.6/drivers/pci/pcie/pme/pcie_pme.h
@@ -0,0 +1,28 @@
+/*
+ * drivers/pci/pcie/pme/pcie_pme.h
+ *
+ * PCI Express Root Port PME signaling support
+ *
+ * Copyright (C) 2009 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc.
+ */
+
+#ifndef _PCIE_PME_H_
+#define _PCIE_PME_H_
+
+struct pcie_device;
+
+#ifdef CONFIG_ACPI
+extern int pcie_pme_acpi_setup(struct pcie_device *srv);
+
+static inline int pcie_pme_platform_notify(struct pcie_device *srv)
+{
+	return pcie_pme_acpi_setup(srv);
+}
+#else /* !CONFIG_ACPI */
+static inline int pcie_pme_platform_notify(struct pcie_device *srv)
+{
+	return 0;
+}
+#endif /* !CONFIG_ACPI */
+
+#endif
Index: linux-2.6/include/linux/pci.h
===================================================================
--- linux-2.6.orig/include/linux/pci.h
+++ linux-2.6/include/linux/pci.h
@@ -239,6 +239,7 @@ struct pci_dev {
 					   configuration space */
 	unsigned int	pme_support:5;	/* Bitmask of states from which PME#
 					   can be generated */
+	unsigned int	pme_interrupt:1;
 	unsigned int	d1_support:1;	/* Low power state D1 is supported */
 	unsigned int	d2_support:1;	/* Low power state D2 is supported */
 	unsigned int	no_d1d2:1;	/* Only allow D0 and D3 */
Index: linux-2.6/Documentation/kernel-parameters.txt
===================================================================
--- linux-2.6.orig/Documentation/kernel-parameters.txt
+++ linux-2.6/Documentation/kernel-parameters.txt
@@ -1986,6 +1986,12 @@ and is between 256 and 4096 characters. 
 		force	Enable ASPM even on devices that claim not to support it.
 			WARNING: Forcing ASPM on may cause system lockups.
 
+	pcie_pme=	[PCIE,PM] Native PCIe PME signaling options:
+		off	Do not use native PCIe PME signaling.
+		force	Use native PCIe PME signaling even if the BIOS refuses
+			to allow the kernel to control the relevant PCIe config
+			registers.
+
 	pcmv=		[HW,PCMCIA] BadgePAD 4
 
 	pd.		[PARIDE]

^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH 4/12] PCI PM: Make it possible to force using INTx for PCIe PME signaling
  2009-12-27 19:57 [PATCH 0/12] PCI run-time PM support (rev. 2) Rafael J. Wysocki
@ 2009-12-27 20:02   ` Rafael J. Wysocki
  2009-12-27 20:00 ` [PATCH 2/12] PCI / PM: Propagate wake-up enable for PCIe devices too Rafael J. Wysocki
                     ` (21 subsequent siblings)
  22 siblings, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2009-12-27 20:02 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Linux PCI, LKML, ACPI Devel Maling List, Francois Romieu,
	pm list, Matthew Garrett

From: Rafael J. Wysocki <rjw@sisk.pl>

Apparently, some machines may have problems with PCI run-time power
management if MSIs are used for the hative PCIe PME signaling.  In
particular, on the MSI Wind U-100 PCIe PME interrupts are not
generated by a PCIe root port after a resume from suspend to RAM, if
the system wake-up was triggered by a PME from the device attached to
this port.  [It doesn't help to free the interrupt on suspend and
request it back on resume, even if that is done along with disabling
the MSI and re-enabling it, respectively.]  However, if INTx
interrupts are used for this purpose on the same machine, everything
works just fine.

For this reason, add a kernel command line switch allowing one to
request that MSIs be not used for the native PCIe PME signaling,
introduce a DMI table allowing us to blacklist machines that need
this switch to be set by default and put the MSI Wind U-100 into this
table.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
 Documentation/kernel-parameters.txt |    2 ++
 drivers/pci/pcie/pme/pcie_pme.c     |   14 +++++++++++++-
 drivers/pci/pcie/portdrv.h          |   17 +++++++++++++++++
 drivers/pci/pcie/portdrv_core.c     |   12 ++++++++++--
 drivers/pci/pcie/portdrv_pci.c      |   27 +++++++++++++++++++++++++++
 5 files changed, 69 insertions(+), 3 deletions(-)

Index: linux-2.6/drivers/pci/pcie/pme/pcie_pme.c
===================================================================
--- linux-2.6.orig/drivers/pci/pcie/pme/pcie_pme.c
+++ linux-2.6/drivers/pci/pcie/pme/pcie_pme.c
@@ -53,12 +53,22 @@ static bool pcie_pme_disabled;
  */
 static bool pcie_pme_force_enable;
 
+/*
+ * If this switch is set, MSI will not be used for PCIe PME signaling.  This
+ * causes the PCIe port driver to use INTx interrupts only, but it turns out
+ * that using MSI for PCIe PME signaling doesn't play well with PCIe PME-based
+ * wake-up from system sleep states.
+ */
+bool pcie_pme_msi_disabled;
+
 static int __init pcie_pme_setup(char *str)
 {
 	if (!strcmp(str, "off"))
 		pcie_pme_disabled = true;
 	else if (!strcmp(str, "force"))
 		pcie_pme_force_enable = true;
+	else if (!strcmp(str, "nomsi"))
+		pcie_pme_msi_disabled = true;
 	return 1;
 }
 __setup("pcie_pme=", pcie_pme_setup);
@@ -73,7 +83,9 @@ __setup("pcie_pme=", pcie_pme_setup);
  */
 static bool pcie_pme_platform_setup(struct pcie_device *srv)
 {
-	return !pcie_pme_platform_notify(srv) || pcie_pme_force_enable;
+	if (!pcie_pme_platform_notify(srv))
+		return true;
+	return pcie_pme_force_enable;
 }
 
 struct pcie_pme_service_data {
Index: linux-2.6/drivers/pci/pcie/portdrv_core.c
===================================================================
--- linux-2.6.orig/drivers/pci/pcie/portdrv_core.c
+++ linux-2.6/drivers/pci/pcie/portdrv_core.c
@@ -186,16 +186,24 @@ static int pcie_port_enable_msix(struct 
  */
 static int init_service_irqs(struct pci_dev *dev, int *irqs, int mask)
 {
-	int i, irq;
+	int i, irq = -1;
+
+	/* We have to use INTx if MSI cannot be used for PCIe PME. */
+	if ((mask & PCIE_PORT_SERVICE_PME) && pcie_pme_no_msi()) {
+		if (dev->pin)
+			irq = dev->irq;
+		goto no_msi;
+	}
 
 	/* Try to use MSI-X if supported */
 	if (!pcie_port_enable_msix(dev, irqs, mask))
 		return 0;
+
 	/* We're not going to use MSI-X, so try MSI and fall back to INTx */
-	irq = -1;
 	if (!pci_enable_msi(dev) || dev->pin)
 		irq = dev->irq;
 
+ no_msi:
 	for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++)
 		irqs[i] = irq;
 	irqs[PCIE_PORT_SERVICE_VC_SHIFT] = -1;
Index: linux-2.6/drivers/pci/pcie/portdrv_pci.c
===================================================================
--- linux-2.6.orig/drivers/pci/pcie/portdrv_pci.c
+++ linux-2.6/drivers/pci/pcie/portdrv_pci.c
@@ -15,6 +15,7 @@
 #include <linux/slab.h>
 #include <linux/pcieport_if.h>
 #include <linux/aer.h>
+#include <linux/dmi.h>
 
 #include "portdrv.h"
 #include "aer/aerdrv.h"
@@ -274,10 +275,36 @@ static struct pci_driver pcie_portdriver
 	.driver.pm 	= PCIE_PORTDRV_PM_OPS,
 };
 
+static int __init dmi_pcie_pme_disable_msi(const struct dmi_system_id *d)
+{
+	pr_notice("%s detected: will not use MSI for PCIe PME signaling\n",
+			d->ident);
+	pcie_pme_disable_msi();
+	return 0;
+}
+
+static struct dmi_system_id __initdata pcie_portdrv_dmi_table[] = {
+	/*
+	 * Boxes that should not use MSI for PCIe PME signaling.
+	 */
+	{
+	 .callback = dmi_pcie_pme_disable_msi,
+	 .ident = "MSI Wind U-100",
+	 .matches = {
+		     DMI_MATCH(DMI_SYS_VENDOR,
+		     		"MICRO-STAR INTERNATIONAL CO., LTD"),
+		     DMI_MATCH(DMI_PRODUCT_NAME, "U-100"),
+		     },
+	 },
+	 {}
+};
+
 static int __init pcie_portdrv_init(void)
 {
 	int retval;
 
+	dmi_check_system(pcie_portdrv_dmi_table);
+
 	retval = pcie_port_bus_register();
 	if (retval) {
 		printk(KERN_WARNING "PCIE: bus_register error: %d\n", retval);
Index: linux-2.6/drivers/pci/pcie/portdrv.h
===================================================================
--- linux-2.6.orig/drivers/pci/pcie/portdrv.h
+++ linux-2.6/drivers/pci/pcie/portdrv.h
@@ -30,4 +30,21 @@ extern void pcie_port_device_remove(stru
 extern int __must_check pcie_port_bus_register(void);
 extern void pcie_port_bus_unregister(void);
 
+#ifdef CONFIG_PCIE_PME
+extern bool pcie_pme_msi_disabled;
+
+static inline void pcie_pme_disable_msi(void)
+{
+	pcie_pme_msi_disabled = true;
+}
+
+static inline bool pcie_pme_no_msi(void)
+{
+	return pcie_pme_msi_disabled;
+}
+#else /* !CONFIG_PCIE_PME */
+static inline void pcie_pme_disable_msi(void) {}
+static inline bool pcie_pme_no_msi(void) { return false; }
+#endif /* !CONFIG_PCIE_PME */
+
 #endif /* _PORTDRV_H_ */
Index: linux-2.6/Documentation/kernel-parameters.txt
===================================================================
--- linux-2.6.orig/Documentation/kernel-parameters.txt
+++ linux-2.6/Documentation/kernel-parameters.txt
@@ -1991,6 +1991,8 @@ and is between 256 and 4096 characters. 
 		force	Use native PCIe PME signaling even if the BIOS refuses
 			to allow the kernel to control the relevant PCIe config
 			registers.
+		nomsi	Do not use MSI for native PCIe PME signaling (this makes
+			all PCIe root ports use INTx for everything).
 
 	pcmv=		[HW,PCMCIA] BadgePAD 4
 

^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH 4/12] PCI PM: Make it possible to force using INTx for PCIe PME signaling
@ 2009-12-27 20:02   ` Rafael J. Wysocki
  0 siblings, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2009-12-27 20:02 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Matthew Garrett, Len Brown, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu

From: Rafael J. Wysocki <rjw@sisk.pl>

Apparently, some machines may have problems with PCI run-time power
management if MSIs are used for the hative PCIe PME signaling.  In
particular, on the MSI Wind U-100 PCIe PME interrupts are not
generated by a PCIe root port after a resume from suspend to RAM, if
the system wake-up was triggered by a PME from the device attached to
this port.  [It doesn't help to free the interrupt on suspend and
request it back on resume, even if that is done along with disabling
the MSI and re-enabling it, respectively.]  However, if INTx
interrupts are used for this purpose on the same machine, everything
works just fine.

For this reason, add a kernel command line switch allowing one to
request that MSIs be not used for the native PCIe PME signaling,
introduce a DMI table allowing us to blacklist machines that need
this switch to be set by default and put the MSI Wind U-100 into this
table.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
 Documentation/kernel-parameters.txt |    2 ++
 drivers/pci/pcie/pme/pcie_pme.c     |   14 +++++++++++++-
 drivers/pci/pcie/portdrv.h          |   17 +++++++++++++++++
 drivers/pci/pcie/portdrv_core.c     |   12 ++++++++++--
 drivers/pci/pcie/portdrv_pci.c      |   27 +++++++++++++++++++++++++++
 5 files changed, 69 insertions(+), 3 deletions(-)

Index: linux-2.6/drivers/pci/pcie/pme/pcie_pme.c
===================================================================
--- linux-2.6.orig/drivers/pci/pcie/pme/pcie_pme.c
+++ linux-2.6/drivers/pci/pcie/pme/pcie_pme.c
@@ -53,12 +53,22 @@ static bool pcie_pme_disabled;
  */
 static bool pcie_pme_force_enable;
 
+/*
+ * If this switch is set, MSI will not be used for PCIe PME signaling.  This
+ * causes the PCIe port driver to use INTx interrupts only, but it turns out
+ * that using MSI for PCIe PME signaling doesn't play well with PCIe PME-based
+ * wake-up from system sleep states.
+ */
+bool pcie_pme_msi_disabled;
+
 static int __init pcie_pme_setup(char *str)
 {
 	if (!strcmp(str, "off"))
 		pcie_pme_disabled = true;
 	else if (!strcmp(str, "force"))
 		pcie_pme_force_enable = true;
+	else if (!strcmp(str, "nomsi"))
+		pcie_pme_msi_disabled = true;
 	return 1;
 }
 __setup("pcie_pme=", pcie_pme_setup);
@@ -73,7 +83,9 @@ __setup("pcie_pme=", pcie_pme_setup);
  */
 static bool pcie_pme_platform_setup(struct pcie_device *srv)
 {
-	return !pcie_pme_platform_notify(srv) || pcie_pme_force_enable;
+	if (!pcie_pme_platform_notify(srv))
+		return true;
+	return pcie_pme_force_enable;
 }
 
 struct pcie_pme_service_data {
Index: linux-2.6/drivers/pci/pcie/portdrv_core.c
===================================================================
--- linux-2.6.orig/drivers/pci/pcie/portdrv_core.c
+++ linux-2.6/drivers/pci/pcie/portdrv_core.c
@@ -186,16 +186,24 @@ static int pcie_port_enable_msix(struct 
  */
 static int init_service_irqs(struct pci_dev *dev, int *irqs, int mask)
 {
-	int i, irq;
+	int i, irq = -1;
+
+	/* We have to use INTx if MSI cannot be used for PCIe PME. */
+	if ((mask & PCIE_PORT_SERVICE_PME) && pcie_pme_no_msi()) {
+		if (dev->pin)
+			irq = dev->irq;
+		goto no_msi;
+	}
 
 	/* Try to use MSI-X if supported */
 	if (!pcie_port_enable_msix(dev, irqs, mask))
 		return 0;
+
 	/* We're not going to use MSI-X, so try MSI and fall back to INTx */
-	irq = -1;
 	if (!pci_enable_msi(dev) || dev->pin)
 		irq = dev->irq;
 
+ no_msi:
 	for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++)
 		irqs[i] = irq;
 	irqs[PCIE_PORT_SERVICE_VC_SHIFT] = -1;
Index: linux-2.6/drivers/pci/pcie/portdrv_pci.c
===================================================================
--- linux-2.6.orig/drivers/pci/pcie/portdrv_pci.c
+++ linux-2.6/drivers/pci/pcie/portdrv_pci.c
@@ -15,6 +15,7 @@
 #include <linux/slab.h>
 #include <linux/pcieport_if.h>
 #include <linux/aer.h>
+#include <linux/dmi.h>
 
 #include "portdrv.h"
 #include "aer/aerdrv.h"
@@ -274,10 +275,36 @@ static struct pci_driver pcie_portdriver
 	.driver.pm 	= PCIE_PORTDRV_PM_OPS,
 };
 
+static int __init dmi_pcie_pme_disable_msi(const struct dmi_system_id *d)
+{
+	pr_notice("%s detected: will not use MSI for PCIe PME signaling\n",
+			d->ident);
+	pcie_pme_disable_msi();
+	return 0;
+}
+
+static struct dmi_system_id __initdata pcie_portdrv_dmi_table[] = {
+	/*
+	 * Boxes that should not use MSI for PCIe PME signaling.
+	 */
+	{
+	 .callback = dmi_pcie_pme_disable_msi,
+	 .ident = "MSI Wind U-100",
+	 .matches = {
+		     DMI_MATCH(DMI_SYS_VENDOR,
+		     		"MICRO-STAR INTERNATIONAL CO., LTD"),
+		     DMI_MATCH(DMI_PRODUCT_NAME, "U-100"),
+		     },
+	 },
+	 {}
+};
+
 static int __init pcie_portdrv_init(void)
 {
 	int retval;
 
+	dmi_check_system(pcie_portdrv_dmi_table);
+
 	retval = pcie_port_bus_register();
 	if (retval) {
 		printk(KERN_WARNING "PCIE: bus_register error: %d\n", retval);
Index: linux-2.6/drivers/pci/pcie/portdrv.h
===================================================================
--- linux-2.6.orig/drivers/pci/pcie/portdrv.h
+++ linux-2.6/drivers/pci/pcie/portdrv.h
@@ -30,4 +30,21 @@ extern void pcie_port_device_remove(stru
 extern int __must_check pcie_port_bus_register(void);
 extern void pcie_port_bus_unregister(void);
 
+#ifdef CONFIG_PCIE_PME
+extern bool pcie_pme_msi_disabled;
+
+static inline void pcie_pme_disable_msi(void)
+{
+	pcie_pme_msi_disabled = true;
+}
+
+static inline bool pcie_pme_no_msi(void)
+{
+	return pcie_pme_msi_disabled;
+}
+#else /* !CONFIG_PCIE_PME */
+static inline void pcie_pme_disable_msi(void) {}
+static inline bool pcie_pme_no_msi(void) { return false; }
+#endif /* !CONFIG_PCIE_PME */
+
 #endif /* _PORTDRV_H_ */
Index: linux-2.6/Documentation/kernel-parameters.txt
===================================================================
--- linux-2.6.orig/Documentation/kernel-parameters.txt
+++ linux-2.6/Documentation/kernel-parameters.txt
@@ -1991,6 +1991,8 @@ and is between 256 and 4096 characters. 
 		force	Use native PCIe PME signaling even if the BIOS refuses
 			to allow the kernel to control the relevant PCIe config
 			registers.
+		nomsi	Do not use MSI for native PCIe PME signaling (this makes
+			all PCIe root ports use INTx for everything).
 
 	pcmv=		[HW,PCMCIA] BadgePAD 4
 


^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH 5/12] ACPI: Add infrastructure for refcounting GPE consumers
  2009-12-27 19:57 [PATCH 0/12] PCI run-time PM support (rev. 2) Rafael J. Wysocki
                   ` (6 preceding siblings ...)
  2009-12-27 20:03 ` [PATCH 5/12] ACPI: Add infrastructure for refcounting GPE consumers Rafael J. Wysocki
@ 2009-12-27 20:03 ` Rafael J. Wysocki
  2009-12-27 20:03   ` Rafael J. Wysocki
                   ` (14 subsequent siblings)
  22 siblings, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2009-12-27 20:03 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Matthew Garrett, Len Brown, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu

From: Matthew Garrett <mjg@redhat.com>

ACPI GPEs may map to multiple devices. The current GPE interface only
provides a mechanism for enabling and disabling GPEs, making it difficult
to change the state of GPEs at runtime without extensive cooperation
between devices. Add an API to allow devices to indicate whether or not
they want their device's GPE to be enabled for both runtime and wakeup
events.

Signed-off-by: Matthew Garrett <mjg@redhat.com>
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
drivers/acpi/acpica/aclocal.h  |    2 +
 drivers/acpi/acpica/aclocal.h  |    2 
 drivers/acpi/acpica/evxfevnt.c |  161 +++++++++++++++++++++++++++++++++++++++++
 include/acpi/acpixf.h          |    8 ++
 3 files changed, 171 insertions(+)

Index: linux-2.6/drivers/acpi/acpica/aclocal.h
===================================================================
--- linux-2.6.orig/drivers/acpi/acpica/aclocal.h
+++ linux-2.6/drivers/acpi/acpica/aclocal.h
@@ -426,6 +426,8 @@ struct acpi_gpe_event_info {
 	struct acpi_gpe_register_info *register_info;	/* Backpointer to register info */
 	u8 flags;		/* Misc info about this GPE */
 	u8 gpe_number;		/* This GPE */
+	u8 runtime_count;
+	u8 wakeup_count;
 };
 
 /* Information about a GPE register pair, one per each status/enable pair in an array */
Index: linux-2.6/drivers/acpi/acpica/evxfevnt.c
===================================================================
--- linux-2.6.orig/drivers/acpi/acpica/evxfevnt.c
+++ linux-2.6/drivers/acpi/acpica/evxfevnt.c
@@ -201,6 +201,167 @@ ACPI_EXPORT_SYMBOL(acpi_enable_event)
 
 /*******************************************************************************
  *
+ * FUNCTION:    acpi_ref_runtime_gpe
+ *
+ * PARAMETERS:  gpe_device      - Parent GPE Device
+ *              gpe_number      - GPE level within the GPE block
+ *
+ * RETURN:      Status
+ *
+ * DESCRIPTION: Take a reference to a runtime GPE
+ *
+ ******************************************************************************/
+acpi_status acpi_ref_runtime_gpe(acpi_handle gpe_device, u32 gpe_number)
+{
+	acpi_status status = AE_OK;
+	acpi_cpu_flags flags;
+	struct acpi_gpe_event_info *gpe_event_info;
+
+	ACPI_FUNCTION_TRACE(acpi_ref_runtime_gpe);
+
+	flags = acpi_os_acquire_lock(acpi_gbl_gpe_lock);
+
+	/* Ensure that we have a valid GPE number */
+
+	gpe_event_info = acpi_ev_get_gpe_event_info(gpe_device, gpe_number);
+	if (!gpe_event_info) {
+		status = AE_BAD_PARAMETER;
+		goto unlock_and_exit;
+	}
+
+	if (++gpe_event_info->runtime_count == 1)
+		status = acpi_ev_enable_gpe(gpe_event_info, TRUE);
+
+	if (ACPI_FAILURE(status))
+		gpe_event_info->runtime_count--;
+
+unlock_and_exit:
+	acpi_os_release_lock(acpi_gbl_gpe_lock, flags);
+	return_ACPI_STATUS(status);
+}
+ACPI_EXPORT_SYMBOL(acpi_ref_runtime_gpe)
+
+/*******************************************************************************
+ *
+ * FUNCTION:    acpi_unref_runtime_gpe
+ *
+ * PARAMETERS:  gpe_device      - Parent GPE Device
+ *              gpe_number      - GPE level within the GPE block
+ *
+ * RETURN:      Status
+ *
+ * DESCRIPTION: Release a reference to a runtime GPE
+ *
+ ******************************************************************************/
+acpi_status acpi_unref_runtime_gpe(acpi_handle gpe_device, u32 gpe_number)
+{
+	acpi_status status = AE_OK;
+	acpi_cpu_flags flags;
+	struct acpi_gpe_event_info *gpe_event_info;
+
+	ACPI_FUNCTION_TRACE(acpi_unref_runtime_gpe);
+
+	flags = acpi_os_acquire_lock(acpi_gbl_gpe_lock);
+
+	/* Ensure that we have a valid GPE number */
+
+	gpe_event_info = acpi_ev_get_gpe_event_info(gpe_device, gpe_number);
+	if (!gpe_event_info) {
+		status = AE_BAD_PARAMETER;
+		goto unlock_and_exit;
+	}
+
+	if (--gpe_event_info->runtime_count == 0)
+		acpi_ev_disable_gpe(gpe_event_info);
+
+unlock_and_exit:
+	acpi_os_release_lock(acpi_gbl_gpe_lock, flags);
+	return_ACPI_STATUS(status);
+}
+ACPI_EXPORT_SYMBOL(acpi_unref_runtime_gpe)
+
+/*******************************************************************************
+ *
+ * FUNCTION:    acpi_ref_wakeup_gpe
+ *
+ * PARAMETERS:  gpe_device      - Parent GPE Device
+ *              gpe_number      - GPE level within the GPE block
+ *
+ * RETURN:      Status
+ *
+ * DESCRIPTION: Take a reference to a wakeup GPE
+ *
+ ******************************************************************************/
+acpi_status acpi_ref_wakeup_gpe(acpi_handle gpe_device, u32 gpe_number)
+{
+	acpi_status status = AE_OK;
+	acpi_cpu_flags flags;
+	struct acpi_gpe_event_info *gpe_event_info;
+
+	ACPI_FUNCTION_TRACE(acpi_ref_wakeup_gpe);
+
+	flags = acpi_os_acquire_lock(acpi_gbl_gpe_lock);
+
+	/* Ensure that we have a valid GPE number */
+
+	gpe_event_info = acpi_ev_get_gpe_event_info(gpe_device, gpe_number);
+	if (!gpe_event_info) {
+		status = AE_BAD_PARAMETER;
+		goto unlock_and_exit;
+	}
+
+	if (++gpe_event_info->wakeup_count == 1)
+		acpi_ev_update_gpe_enable_masks(gpe_event_info,
+						ACPI_GPE_ENABLE);
+
+unlock_and_exit:
+	acpi_os_release_lock(acpi_gbl_gpe_lock, flags);
+	return_ACPI_STATUS(status);
+}
+ACPI_EXPORT_SYMBOL(acpi_ref_wakeup_gpe)
+
+/*******************************************************************************
+ *
+ * FUNCTION:    acpi_unref_wakeup_gpe
+ *
+ * PARAMETERS:  gpe_device      - Parent GPE Device
+ *              gpe_number      - GPE level within the GPE block
+ *
+ * RETURN:      Status
+ *
+ * DESCRIPTION: Release a reference to a wakeup GPE
+ *
+ ******************************************************************************/
+acpi_status acpi_unref_wakeup_gpe(acpi_handle gpe_device, u32 gpe_number)
+{
+	acpi_status status = AE_OK;
+	acpi_cpu_flags flags;
+	struct acpi_gpe_event_info *gpe_event_info;
+
+	ACPI_FUNCTION_TRACE(acpi_unref_wakeup_gpe);
+
+	flags = acpi_os_acquire_lock(acpi_gbl_gpe_lock);
+
+	/* Ensure that we have a valid GPE number */
+
+	gpe_event_info = acpi_ev_get_gpe_event_info(gpe_device, gpe_number);
+	if (!gpe_event_info) {
+		status = AE_BAD_PARAMETER;
+		goto unlock_and_exit;
+	}
+
+	if (--gpe_event_info->wakeup_count == 0)
+		acpi_ev_update_gpe_enable_masks(gpe_event_info,
+			ACPI_GPE_DISABLE);
+
+unlock_and_exit:
+	acpi_os_release_lock(acpi_gbl_gpe_lock, flags);
+	return_ACPI_STATUS(status);
+}
+ACPI_EXPORT_SYMBOL(acpi_unref_wakeup_gpe)
+
+/*******************************************************************************
+ *
  * FUNCTION:    acpi_set_gpe_type
  *
  * PARAMETERS:  gpe_device      - Parent GPE Device
Index: linux-2.6/include/acpi/acpixf.h
===================================================================
--- linux-2.6.orig/include/acpi/acpixf.h
+++ linux-2.6/include/acpi/acpixf.h
@@ -283,6 +283,14 @@ acpi_status acpi_get_event_status(u32 ev
  */
 acpi_status acpi_set_gpe_type(acpi_handle gpe_device, u32 gpe_number, u8 type);
 
+acpi_status acpi_ref_runtime_gpe(acpi_handle gpe_device, u32 gpe_number);
+
+acpi_status acpi_unref_runtime_gpe(acpi_handle gpe_device, u32 gpe_number);
+
+acpi_status acpi_ref_wakeup_gpe(acpi_handle gpe_device, u32 gpe_number);
+
+acpi_status acpi_unref_wakeup_gpe(acpi_handle gpe_device, u32 gpe_number);
+
 acpi_status acpi_enable_gpe(acpi_handle gpe_device, u32 gpe_number);
 
 acpi_status acpi_disable_gpe(acpi_handle gpe_device, u32 gpe_number);

^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH 5/12] ACPI: Add infrastructure for refcounting GPE consumers
  2009-12-27 19:57 [PATCH 0/12] PCI run-time PM support (rev. 2) Rafael J. Wysocki
                   ` (5 preceding siblings ...)
  2009-12-27 20:02   ` Rafael J. Wysocki
@ 2009-12-27 20:03 ` Rafael J. Wysocki
  2009-12-27 20:03 ` Rafael J. Wysocki
                   ` (15 subsequent siblings)
  22 siblings, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2009-12-27 20:03 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Linux PCI, LKML, ACPI Devel Maling List, Francois Romieu,
	pm list, Matthew Garrett

From: Matthew Garrett <mjg@redhat.com>

ACPI GPEs may map to multiple devices. The current GPE interface only
provides a mechanism for enabling and disabling GPEs, making it difficult
to change the state of GPEs at runtime without extensive cooperation
between devices. Add an API to allow devices to indicate whether or not
they want their device's GPE to be enabled for both runtime and wakeup
events.

Signed-off-by: Matthew Garrett <mjg@redhat.com>
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
drivers/acpi/acpica/aclocal.h  |    2 +
 drivers/acpi/acpica/aclocal.h  |    2 
 drivers/acpi/acpica/evxfevnt.c |  161 +++++++++++++++++++++++++++++++++++++++++
 include/acpi/acpixf.h          |    8 ++
 3 files changed, 171 insertions(+)

Index: linux-2.6/drivers/acpi/acpica/aclocal.h
===================================================================
--- linux-2.6.orig/drivers/acpi/acpica/aclocal.h
+++ linux-2.6/drivers/acpi/acpica/aclocal.h
@@ -426,6 +426,8 @@ struct acpi_gpe_event_info {
 	struct acpi_gpe_register_info *register_info;	/* Backpointer to register info */
 	u8 flags;		/* Misc info about this GPE */
 	u8 gpe_number;		/* This GPE */
+	u8 runtime_count;
+	u8 wakeup_count;
 };
 
 /* Information about a GPE register pair, one per each status/enable pair in an array */
Index: linux-2.6/drivers/acpi/acpica/evxfevnt.c
===================================================================
--- linux-2.6.orig/drivers/acpi/acpica/evxfevnt.c
+++ linux-2.6/drivers/acpi/acpica/evxfevnt.c
@@ -201,6 +201,167 @@ ACPI_EXPORT_SYMBOL(acpi_enable_event)
 
 /*******************************************************************************
  *
+ * FUNCTION:    acpi_ref_runtime_gpe
+ *
+ * PARAMETERS:  gpe_device      - Parent GPE Device
+ *              gpe_number      - GPE level within the GPE block
+ *
+ * RETURN:      Status
+ *
+ * DESCRIPTION: Take a reference to a runtime GPE
+ *
+ ******************************************************************************/
+acpi_status acpi_ref_runtime_gpe(acpi_handle gpe_device, u32 gpe_number)
+{
+	acpi_status status = AE_OK;
+	acpi_cpu_flags flags;
+	struct acpi_gpe_event_info *gpe_event_info;
+
+	ACPI_FUNCTION_TRACE(acpi_ref_runtime_gpe);
+
+	flags = acpi_os_acquire_lock(acpi_gbl_gpe_lock);
+
+	/* Ensure that we have a valid GPE number */
+
+	gpe_event_info = acpi_ev_get_gpe_event_info(gpe_device, gpe_number);
+	if (!gpe_event_info) {
+		status = AE_BAD_PARAMETER;
+		goto unlock_and_exit;
+	}
+
+	if (++gpe_event_info->runtime_count == 1)
+		status = acpi_ev_enable_gpe(gpe_event_info, TRUE);
+
+	if (ACPI_FAILURE(status))
+		gpe_event_info->runtime_count--;
+
+unlock_and_exit:
+	acpi_os_release_lock(acpi_gbl_gpe_lock, flags);
+	return_ACPI_STATUS(status);
+}
+ACPI_EXPORT_SYMBOL(acpi_ref_runtime_gpe)
+
+/*******************************************************************************
+ *
+ * FUNCTION:    acpi_unref_runtime_gpe
+ *
+ * PARAMETERS:  gpe_device      - Parent GPE Device
+ *              gpe_number      - GPE level within the GPE block
+ *
+ * RETURN:      Status
+ *
+ * DESCRIPTION: Release a reference to a runtime GPE
+ *
+ ******************************************************************************/
+acpi_status acpi_unref_runtime_gpe(acpi_handle gpe_device, u32 gpe_number)
+{
+	acpi_status status = AE_OK;
+	acpi_cpu_flags flags;
+	struct acpi_gpe_event_info *gpe_event_info;
+
+	ACPI_FUNCTION_TRACE(acpi_unref_runtime_gpe);
+
+	flags = acpi_os_acquire_lock(acpi_gbl_gpe_lock);
+
+	/* Ensure that we have a valid GPE number */
+
+	gpe_event_info = acpi_ev_get_gpe_event_info(gpe_device, gpe_number);
+	if (!gpe_event_info) {
+		status = AE_BAD_PARAMETER;
+		goto unlock_and_exit;
+	}
+
+	if (--gpe_event_info->runtime_count == 0)
+		acpi_ev_disable_gpe(gpe_event_info);
+
+unlock_and_exit:
+	acpi_os_release_lock(acpi_gbl_gpe_lock, flags);
+	return_ACPI_STATUS(status);
+}
+ACPI_EXPORT_SYMBOL(acpi_unref_runtime_gpe)
+
+/*******************************************************************************
+ *
+ * FUNCTION:    acpi_ref_wakeup_gpe
+ *
+ * PARAMETERS:  gpe_device      - Parent GPE Device
+ *              gpe_number      - GPE level within the GPE block
+ *
+ * RETURN:      Status
+ *
+ * DESCRIPTION: Take a reference to a wakeup GPE
+ *
+ ******************************************************************************/
+acpi_status acpi_ref_wakeup_gpe(acpi_handle gpe_device, u32 gpe_number)
+{
+	acpi_status status = AE_OK;
+	acpi_cpu_flags flags;
+	struct acpi_gpe_event_info *gpe_event_info;
+
+	ACPI_FUNCTION_TRACE(acpi_ref_wakeup_gpe);
+
+	flags = acpi_os_acquire_lock(acpi_gbl_gpe_lock);
+
+	/* Ensure that we have a valid GPE number */
+
+	gpe_event_info = acpi_ev_get_gpe_event_info(gpe_device, gpe_number);
+	if (!gpe_event_info) {
+		status = AE_BAD_PARAMETER;
+		goto unlock_and_exit;
+	}
+
+	if (++gpe_event_info->wakeup_count == 1)
+		acpi_ev_update_gpe_enable_masks(gpe_event_info,
+						ACPI_GPE_ENABLE);
+
+unlock_and_exit:
+	acpi_os_release_lock(acpi_gbl_gpe_lock, flags);
+	return_ACPI_STATUS(status);
+}
+ACPI_EXPORT_SYMBOL(acpi_ref_wakeup_gpe)
+
+/*******************************************************************************
+ *
+ * FUNCTION:    acpi_unref_wakeup_gpe
+ *
+ * PARAMETERS:  gpe_device      - Parent GPE Device
+ *              gpe_number      - GPE level within the GPE block
+ *
+ * RETURN:      Status
+ *
+ * DESCRIPTION: Release a reference to a wakeup GPE
+ *
+ ******************************************************************************/
+acpi_status acpi_unref_wakeup_gpe(acpi_handle gpe_device, u32 gpe_number)
+{
+	acpi_status status = AE_OK;
+	acpi_cpu_flags flags;
+	struct acpi_gpe_event_info *gpe_event_info;
+
+	ACPI_FUNCTION_TRACE(acpi_unref_wakeup_gpe);
+
+	flags = acpi_os_acquire_lock(acpi_gbl_gpe_lock);
+
+	/* Ensure that we have a valid GPE number */
+
+	gpe_event_info = acpi_ev_get_gpe_event_info(gpe_device, gpe_number);
+	if (!gpe_event_info) {
+		status = AE_BAD_PARAMETER;
+		goto unlock_and_exit;
+	}
+
+	if (--gpe_event_info->wakeup_count == 0)
+		acpi_ev_update_gpe_enable_masks(gpe_event_info,
+			ACPI_GPE_DISABLE);
+
+unlock_and_exit:
+	acpi_os_release_lock(acpi_gbl_gpe_lock, flags);
+	return_ACPI_STATUS(status);
+}
+ACPI_EXPORT_SYMBOL(acpi_unref_wakeup_gpe)
+
+/*******************************************************************************
+ *
  * FUNCTION:    acpi_set_gpe_type
  *
  * PARAMETERS:  gpe_device      - Parent GPE Device
Index: linux-2.6/include/acpi/acpixf.h
===================================================================
--- linux-2.6.orig/include/acpi/acpixf.h
+++ linux-2.6/include/acpi/acpixf.h
@@ -283,6 +283,14 @@ acpi_status acpi_get_event_status(u32 ev
  */
 acpi_status acpi_set_gpe_type(acpi_handle gpe_device, u32 gpe_number, u8 type);
 
+acpi_status acpi_ref_runtime_gpe(acpi_handle gpe_device, u32 gpe_number);
+
+acpi_status acpi_unref_runtime_gpe(acpi_handle gpe_device, u32 gpe_number);
+
+acpi_status acpi_ref_wakeup_gpe(acpi_handle gpe_device, u32 gpe_number);
+
+acpi_status acpi_unref_wakeup_gpe(acpi_handle gpe_device, u32 gpe_number);
+
 acpi_status acpi_enable_gpe(acpi_handle gpe_device, u32 gpe_number);
 
 acpi_status acpi_disable_gpe(acpi_handle gpe_device, u32 gpe_number);

^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH 6/12] ACPI: Add support for new refcounted GPE API to drivers
  2009-12-27 19:57 [PATCH 0/12] PCI run-time PM support (rev. 2) Rafael J. Wysocki
@ 2009-12-27 20:03   ` Rafael J. Wysocki
  2009-12-27 20:00 ` [PATCH 2/12] PCI / PM: Propagate wake-up enable for PCIe devices too Rafael J. Wysocki
                     ` (21 subsequent siblings)
  22 siblings, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2009-12-27 20:03 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Linux PCI, LKML, ACPI Devel Maling List, Francois Romieu,
	pm list, Matthew Garrett

From: Matthew Garrett <mjg@redhat.com>

Add GPE refcounting support to ACPI drivers that need it. This will
currently do little until the core is changed over to use the new
behaviour.

Signed-off-by: Matthew Garrett <mjg@redhat.com>
Signed-off-by; Rafael J. Wysocki <rjw@sisk.pl>
---
drivers/acpi/button.c |   12 ++++++++++++
 drivers/acpi/button.c |   12 ++++++++++++
 drivers/acpi/ec.c     |    4 +++-
 drivers/acpi/wakeup.c |    6 ++++++
 3 files changed, 21 insertions(+), 1 deletion(-)

Index: linux-2.6/drivers/acpi/button.c
===================================================================
--- linux-2.6.orig/drivers/acpi/button.c
+++ linux-2.6/drivers/acpi/button.c
@@ -427,6 +427,10 @@ static int acpi_button_add(struct acpi_d
 				  ACPI_GPE_TYPE_WAKE_RUN);
 		acpi_enable_gpe(device->wakeup.gpe_device,
 				device->wakeup.gpe_number);
+		acpi_ref_runtime_gpe(device->wakeup.gpe_device,
+				     device->wakeup.gpe_number);
+		acpi_ref_wakeup_gpe(device->wakeup.gpe_device,
+				    device->wakeup.gpe_number);
 		device->wakeup.state.enabled = 1;
 	}
 
@@ -446,6 +450,14 @@ static int acpi_button_remove(struct acp
 {
 	struct acpi_button *button = acpi_driver_data(device);
 
+	if (device->wakeup.flags.valid) {
+		acpi_unref_runtime_gpe(device->wakeup.gpe_device,
+			     device->wakeup.gpe_number);
+		acpi_unref_wakeup_gpe(device->wakeup.gpe_device,
+				      device->wakeup.gpe_number);
+		device->wakeup.state.enabled = 0;
+	}
+
 	acpi_button_remove_fs(device);
 	input_unregister_device(button->input);
 	kfree(button);
Index: linux-2.6/drivers/acpi/ec.c
===================================================================
--- linux-2.6.orig/drivers/acpi/ec.c
+++ linux-2.6/drivers/acpi/ec.c
@@ -755,7 +755,7 @@ static int ec_install_handlers(struct ac
 	if (ACPI_FAILURE(status))
 		return -ENODEV;
 	acpi_set_gpe_type(NULL, ec->gpe, ACPI_GPE_TYPE_RUNTIME);
-	acpi_enable_gpe(NULL, ec->gpe);
+	acpi_ref_runtime_gpe(NULL, ec->gpe);
 	status = acpi_install_address_space_handler(ec->handle,
 						    ACPI_ADR_SPACE_EC,
 						    &acpi_ec_space_handler,
@@ -772,6 +772,7 @@ static int ec_install_handlers(struct ac
 		} else {
 			acpi_remove_gpe_handler(NULL, ec->gpe,
 				&acpi_ec_gpe_handler);
+                        acpi_unref_runtime_gpe(NULL, ec->gpe);
 			return -ENODEV;
 		}
 	}
@@ -782,6 +783,7 @@ static int ec_install_handlers(struct ac
 
 static void ec_remove_handlers(struct acpi_ec *ec)
 {
+	acpi_unref_runtime_gpe(NULL, ec->gpe);
 	if (ACPI_FAILURE(acpi_remove_address_space_handler(ec->handle,
 				ACPI_ADR_SPACE_EC, &acpi_ec_space_handler)))
 		pr_err(PREFIX "failed to remove space handler\n");
Index: linux-2.6/drivers/acpi/wakeup.c
===================================================================
--- linux-2.6.orig/drivers/acpi/wakeup.c
+++ linux-2.6/drivers/acpi/wakeup.c
@@ -81,6 +81,8 @@ void acpi_enable_wakeup_device(u8 sleep_
 		if (!dev->wakeup.flags.run_wake)
 			acpi_enable_gpe(dev->wakeup.gpe_device,
 					dev->wakeup.gpe_number);
+		acpi_ref_wakeup_gpe(dev->wakeup.gpe_device,
+				    dev->wakeup.gpe_number);
 	}
 }
 
@@ -121,6 +123,8 @@ void acpi_disable_wakeup_device(u8 sleep
 			acpi_clear_gpe(dev->wakeup.gpe_device,
 				       dev->wakeup.gpe_number, ACPI_NOT_ISR);
 		}
+		acpi_unref_wakeup_gpe(dev->wakeup.gpe_device,
+				    dev->wakeup.gpe_number);
 	}
 }
 
@@ -141,6 +145,8 @@ int __init acpi_wakeup_device_init(void)
 				  ACPI_GPE_TYPE_WAKE_RUN);
 		acpi_enable_gpe(dev->wakeup.gpe_device,
 				dev->wakeup.gpe_number);
+		acpi_ref_wakeup_gpe(dev->wakeup.gpe_device,
+				    dev->wakeup.gpe_number);
 		dev->wakeup.state.enabled = 1;
 	}
 	mutex_unlock(&acpi_device_lock);

^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH 6/12] ACPI: Add support for new refcounted GPE API to drivers
@ 2009-12-27 20:03   ` Rafael J. Wysocki
  0 siblings, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2009-12-27 20:03 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Matthew Garrett, Len Brown, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu

From: Matthew Garrett <mjg@redhat.com>

Add GPE refcounting support to ACPI drivers that need it. This will
currently do little until the core is changed over to use the new
behaviour.

Signed-off-by: Matthew Garrett <mjg@redhat.com>
Signed-off-by; Rafael J. Wysocki <rjw@sisk.pl>
---
drivers/acpi/button.c |   12 ++++++++++++
 drivers/acpi/button.c |   12 ++++++++++++
 drivers/acpi/ec.c     |    4 +++-
 drivers/acpi/wakeup.c |    6 ++++++
 3 files changed, 21 insertions(+), 1 deletion(-)

Index: linux-2.6/drivers/acpi/button.c
===================================================================
--- linux-2.6.orig/drivers/acpi/button.c
+++ linux-2.6/drivers/acpi/button.c
@@ -427,6 +427,10 @@ static int acpi_button_add(struct acpi_d
 				  ACPI_GPE_TYPE_WAKE_RUN);
 		acpi_enable_gpe(device->wakeup.gpe_device,
 				device->wakeup.gpe_number);
+		acpi_ref_runtime_gpe(device->wakeup.gpe_device,
+				     device->wakeup.gpe_number);
+		acpi_ref_wakeup_gpe(device->wakeup.gpe_device,
+				    device->wakeup.gpe_number);
 		device->wakeup.state.enabled = 1;
 	}
 
@@ -446,6 +450,14 @@ static int acpi_button_remove(struct acp
 {
 	struct acpi_button *button = acpi_driver_data(device);
 
+	if (device->wakeup.flags.valid) {
+		acpi_unref_runtime_gpe(device->wakeup.gpe_device,
+			     device->wakeup.gpe_number);
+		acpi_unref_wakeup_gpe(device->wakeup.gpe_device,
+				      device->wakeup.gpe_number);
+		device->wakeup.state.enabled = 0;
+	}
+
 	acpi_button_remove_fs(device);
 	input_unregister_device(button->input);
 	kfree(button);
Index: linux-2.6/drivers/acpi/ec.c
===================================================================
--- linux-2.6.orig/drivers/acpi/ec.c
+++ linux-2.6/drivers/acpi/ec.c
@@ -755,7 +755,7 @@ static int ec_install_handlers(struct ac
 	if (ACPI_FAILURE(status))
 		return -ENODEV;
 	acpi_set_gpe_type(NULL, ec->gpe, ACPI_GPE_TYPE_RUNTIME);
-	acpi_enable_gpe(NULL, ec->gpe);
+	acpi_ref_runtime_gpe(NULL, ec->gpe);
 	status = acpi_install_address_space_handler(ec->handle,
 						    ACPI_ADR_SPACE_EC,
 						    &acpi_ec_space_handler,
@@ -772,6 +772,7 @@ static int ec_install_handlers(struct ac
 		} else {
 			acpi_remove_gpe_handler(NULL, ec->gpe,
 				&acpi_ec_gpe_handler);
+                        acpi_unref_runtime_gpe(NULL, ec->gpe);
 			return -ENODEV;
 		}
 	}
@@ -782,6 +783,7 @@ static int ec_install_handlers(struct ac
 
 static void ec_remove_handlers(struct acpi_ec *ec)
 {
+	acpi_unref_runtime_gpe(NULL, ec->gpe);
 	if (ACPI_FAILURE(acpi_remove_address_space_handler(ec->handle,
 				ACPI_ADR_SPACE_EC, &acpi_ec_space_handler)))
 		pr_err(PREFIX "failed to remove space handler\n");
Index: linux-2.6/drivers/acpi/wakeup.c
===================================================================
--- linux-2.6.orig/drivers/acpi/wakeup.c
+++ linux-2.6/drivers/acpi/wakeup.c
@@ -81,6 +81,8 @@ void acpi_enable_wakeup_device(u8 sleep_
 		if (!dev->wakeup.flags.run_wake)
 			acpi_enable_gpe(dev->wakeup.gpe_device,
 					dev->wakeup.gpe_number);
+		acpi_ref_wakeup_gpe(dev->wakeup.gpe_device,
+				    dev->wakeup.gpe_number);
 	}
 }
 
@@ -121,6 +123,8 @@ void acpi_disable_wakeup_device(u8 sleep
 			acpi_clear_gpe(dev->wakeup.gpe_device,
 				       dev->wakeup.gpe_number, ACPI_NOT_ISR);
 		}
+		acpi_unref_wakeup_gpe(dev->wakeup.gpe_device,
+				    dev->wakeup.gpe_number);
 	}
 }
 
@@ -141,6 +145,8 @@ int __init acpi_wakeup_device_init(void)
 				  ACPI_GPE_TYPE_WAKE_RUN);
 		acpi_enable_gpe(dev->wakeup.gpe_device,
 				dev->wakeup.gpe_number);
+		acpi_ref_wakeup_gpe(dev->wakeup.gpe_device,
+				    dev->wakeup.gpe_number);
 		dev->wakeup.state.enabled = 1;
 	}
 	mutex_unlock(&acpi_device_lock);


^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH 7/12] ACPI: Remove old GPE API and transition code entirely to new one
  2009-12-27 19:57 [PATCH 0/12] PCI run-time PM support (rev. 2) Rafael J. Wysocki
                   ` (9 preceding siblings ...)
  2009-12-27 20:04 ` [PATCH 7/12] ACPI: Remove old GPE API and transition code entirely to new one Rafael J. Wysocki
@ 2009-12-27 20:04 ` Rafael J. Wysocki
  2009-12-27 20:05 ` [PATCH 8/12] ACPI / PM: Add more run-time wake-up fields Rafael J. Wysocki
                   ` (11 subsequent siblings)
  22 siblings, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2009-12-27 20:04 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Matthew Garrett, Len Brown, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu

From: Matthew Garrett <mjg@redhat.com>

Remove the old GPE type handling entirely, which gets rid of various quirks
like the implicit disabling with GPE type setting. This requires a small
amount of rework in order to ensure that non-wake GPEs are enabled by
default to preserve existing behaviour.

Signed-off-by: Matthew Garrett <mjg@redhat.com>
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
drivers/acpi/acpica/acevents.h |    6 +--
 drivers/acpi/acpica/acevents.h |    6 -
 drivers/acpi/acpica/evgpe.c    |  147 +++--------------------------------------
 drivers/acpi/acpica/evgpeblk.c |   69 ++++++-------------
 drivers/acpi/acpica/evxface.c  |   14 ---
 drivers/acpi/acpica/evxfevnt.c |   48 -------------
 drivers/acpi/button.c          |    5 -
 drivers/acpi/ec.c              |    2 
 drivers/acpi/wakeup.c          |   55 ++-------------
 include/acpi/actypes.h         |   32 ++------
 9 files changed, 59 insertions(+), 319 deletions(-)

Index: linux-2.6/drivers/acpi/acpica/acevents.h
===================================================================
--- linux-2.6.orig/drivers/acpi/acpica/acevents.h
+++ linux-2.6/drivers/acpi/acpica/acevents.h
@@ -76,8 +76,7 @@ acpi_ev_queue_notify_request(struct acpi
  * evgpe - GPE handling and dispatch
  */
 acpi_status
-acpi_ev_update_gpe_enable_masks(struct acpi_gpe_event_info *gpe_event_info,
-				u8 type);
+acpi_ev_update_gpe_enable_masks(struct acpi_gpe_event_info *gpe_event_info);
 
 acpi_status
 acpi_ev_enable_gpe(struct acpi_gpe_event_info *gpe_event_info,
@@ -122,9 +121,6 @@ acpi_ev_gpe_dispatch(struct acpi_gpe_eve
 u32 acpi_ev_gpe_detect(struct acpi_gpe_xrupt_info *gpe_xrupt_list);
 
 acpi_status
-acpi_ev_set_gpe_type(struct acpi_gpe_event_info *gpe_event_info, u8 type);
-
-acpi_status
 acpi_ev_check_for_wake_only_gpe(struct acpi_gpe_event_info *gpe_event_info);
 
 acpi_status acpi_ev_gpe_initialize(void);
Index: linux-2.6/drivers/acpi/acpica/evgpe.c
===================================================================
--- linux-2.6.orig/drivers/acpi/acpica/evgpe.c
+++ linux-2.6/drivers/acpi/acpica/evgpe.c
@@ -54,54 +54,9 @@ static void ACPI_SYSTEM_XFACE acpi_ev_as
 
 /*******************************************************************************
  *
- * FUNCTION:    acpi_ev_set_gpe_type
- *
- * PARAMETERS:  gpe_event_info          - GPE to set
- *              Type                    - New type
- *
- * RETURN:      Status
- *
- * DESCRIPTION: Sets the new type for the GPE (wake, run, or wake/run)
- *
- ******************************************************************************/
-
-acpi_status
-acpi_ev_set_gpe_type(struct acpi_gpe_event_info *gpe_event_info, u8 type)
-{
-	acpi_status status;
-
-	ACPI_FUNCTION_TRACE(ev_set_gpe_type);
-
-	/* Validate type and update register enable masks */
-
-	switch (type) {
-	case ACPI_GPE_TYPE_WAKE:
-	case ACPI_GPE_TYPE_RUNTIME:
-	case ACPI_GPE_TYPE_WAKE_RUN:
-		break;
-
-	default:
-		return_ACPI_STATUS(AE_BAD_PARAMETER);
-	}
-
-	/* Disable the GPE if currently enabled */
-
-	status = acpi_ev_disable_gpe(gpe_event_info);
-
-	/* Clear the type bits and insert the new Type */
-
-	gpe_event_info->flags &= ~ACPI_GPE_TYPE_MASK;
-	gpe_event_info->flags |= type;
-	return_ACPI_STATUS(status);
-}
-
-/*******************************************************************************
- *
  * FUNCTION:    acpi_ev_update_gpe_enable_masks
  *
  * PARAMETERS:  gpe_event_info          - GPE to update
- *              Type                    - What to do: ACPI_GPE_DISABLE or
- *                                        ACPI_GPE_ENABLE
  *
  * RETURN:      Status
  *
@@ -110,8 +65,7 @@ acpi_ev_set_gpe_type(struct acpi_gpe_eve
  ******************************************************************************/
 
 acpi_status
-acpi_ev_update_gpe_enable_masks(struct acpi_gpe_event_info *gpe_event_info,
-				u8 type)
+acpi_ev_update_gpe_enable_masks(struct acpi_gpe_event_info *gpe_event_info)
 {
 	struct acpi_gpe_register_info *gpe_register_info;
 	u8 register_bit;
@@ -127,37 +81,13 @@ acpi_ev_update_gpe_enable_masks(struct a
 	    (1 <<
 	     (gpe_event_info->gpe_number - gpe_register_info->base_gpe_number));
 
-	/* 1) Disable case. Simply clear all enable bits */
-
-	if (type == ACPI_GPE_DISABLE) {
-		ACPI_CLEAR_BIT(gpe_register_info->enable_for_wake,
-			       register_bit);
-		ACPI_CLEAR_BIT(gpe_register_info->enable_for_run, register_bit);
-		return_ACPI_STATUS(AE_OK);
-	}
-
-	/* 2) Enable case. Set/Clear the appropriate enable bits */
-
-	switch (gpe_event_info->flags & ACPI_GPE_TYPE_MASK) {
-	case ACPI_GPE_TYPE_WAKE:
-		ACPI_SET_BIT(gpe_register_info->enable_for_wake, register_bit);
-		ACPI_CLEAR_BIT(gpe_register_info->enable_for_run, register_bit);
-		break;
+	ACPI_CLEAR_BIT(gpe_register_info->enable_for_wake, register_bit);
+	ACPI_CLEAR_BIT(gpe_register_info->enable_for_run, register_bit);
 
-	case ACPI_GPE_TYPE_RUNTIME:
-		ACPI_CLEAR_BIT(gpe_register_info->enable_for_wake,
-			       register_bit);
+	if (gpe_event_info->runtime_count)
 		ACPI_SET_BIT(gpe_register_info->enable_for_run, register_bit);
-		break;
-
-	case ACPI_GPE_TYPE_WAKE_RUN:
+	if (gpe_event_info->wakeup_count)
 		ACPI_SET_BIT(gpe_register_info->enable_for_wake, register_bit);
-		ACPI_SET_BIT(gpe_register_info->enable_for_run, register_bit);
-		break;
-
-	default:
-		return_ACPI_STATUS(AE_BAD_PARAMETER);
-	}
 
 	return_ACPI_STATUS(AE_OK);
 }
@@ -186,47 +116,21 @@ acpi_ev_enable_gpe(struct acpi_gpe_event
 
 	/* Make sure HW enable masks are updated */
 
-	status =
-	    acpi_ev_update_gpe_enable_masks(gpe_event_info, ACPI_GPE_ENABLE);
+	status = acpi_ev_update_gpe_enable_masks(gpe_event_info);
 	if (ACPI_FAILURE(status)) {
 		return_ACPI_STATUS(status);
 	}
 
 	/* Mark wake-enabled or HW enable, or both */
 
-	switch (gpe_event_info->flags & ACPI_GPE_TYPE_MASK) {
-	case ACPI_GPE_TYPE_WAKE:
-
-		ACPI_SET_BIT(gpe_event_info->flags, ACPI_GPE_WAKE_ENABLED);
-		break;
-
-	case ACPI_GPE_TYPE_WAKE_RUN:
-
-		ACPI_SET_BIT(gpe_event_info->flags, ACPI_GPE_WAKE_ENABLED);
-
-		/*lint -fallthrough */
-
-	case ACPI_GPE_TYPE_RUNTIME:
-
-		ACPI_SET_BIT(gpe_event_info->flags, ACPI_GPE_RUN_ENABLED);
-
-		if (write_to_hardware) {
-
-			/* Clear the GPE (of stale events), then enable it */
-
-			status = acpi_hw_clear_gpe(gpe_event_info);
-			if (ACPI_FAILURE(status)) {
-				return_ACPI_STATUS(status);
-			}
-
-			/* Enable the requested runtime GPE */
-
-			status = acpi_hw_write_gpe_enable_reg(gpe_event_info);
-		}
-		break;
+	if (gpe_event_info->runtime_count && write_to_hardware) {
+		/* Clear the GPE (of stale events), then enable it */
+		status = acpi_hw_clear_gpe(gpe_event_info);
+		if (ACPI_FAILURE(status))
+			return_ACPI_STATUS(status);
 
-	default:
-		return_ACPI_STATUS(AE_BAD_PARAMETER);
+		/* Enable the requested runtime GPE */
+		status = acpi_hw_write_gpe_enable_reg(gpe_event_info);
 	}
 
 	return_ACPI_STATUS(AE_OK);
@@ -253,34 +157,11 @@ acpi_status acpi_ev_disable_gpe(struct a
 	/* Make sure HW enable masks are updated */
 
 	status =
-	    acpi_ev_update_gpe_enable_masks(gpe_event_info, ACPI_GPE_DISABLE);
+	    acpi_ev_update_gpe_enable_masks(gpe_event_info);
 	if (ACPI_FAILURE(status)) {
 		return_ACPI_STATUS(status);
 	}
 
-	/* Clear the appropriate enabled flags for this GPE */
-
-	switch (gpe_event_info->flags & ACPI_GPE_TYPE_MASK) {
-	case ACPI_GPE_TYPE_WAKE:
-		ACPI_CLEAR_BIT(gpe_event_info->flags, ACPI_GPE_WAKE_ENABLED);
-		break;
-
-	case ACPI_GPE_TYPE_WAKE_RUN:
-		ACPI_CLEAR_BIT(gpe_event_info->flags, ACPI_GPE_WAKE_ENABLED);
-
-		/* fallthrough */
-
-	case ACPI_GPE_TYPE_RUNTIME:
-
-		/* Disable the requested runtime GPE */
-
-		ACPI_CLEAR_BIT(gpe_event_info->flags, ACPI_GPE_RUN_ENABLED);
-		break;
-
-	default:
-		break;
-	}
-
 	/*
 	 * Even if we don't know the GPE type, make sure that we always
 	 * disable it. low_disable_gpe will just clear the enable bit for this
Index: linux-2.6/drivers/acpi/acpica/evgpeblk.c
===================================================================
--- linux-2.6.orig/drivers/acpi/acpica/evgpeblk.c
+++ linux-2.6/drivers/acpi/acpica/evgpeblk.c
@@ -325,22 +325,16 @@ acpi_ev_save_method_info(acpi_handle obj
 
 	/*
 	 * Now we can add this information to the gpe_event_info block for use
-	 * during dispatch of this GPE. Default type is RUNTIME, although this may
-	 * change when the _PRW methods are executed later.
+	 * during dispatch of this GPE.
 	 */
 	gpe_event_info =
 	    &gpe_block->event_info[gpe_number - gpe_block->block_base_number];
 
-	gpe_event_info->flags = (u8)
-	    (type | ACPI_GPE_DISPATCH_METHOD | ACPI_GPE_TYPE_RUNTIME);
+	gpe_event_info->flags = (u8) (type | ACPI_GPE_DISPATCH_METHOD);
 
 	gpe_event_info->dispatch.method_node =
 	    (struct acpi_namespace_node *)obj_handle;
 
-	/* Update enable mask, but don't enable the HW GPE as of yet */
-
-	status = acpi_ev_enable_gpe(gpe_event_info, FALSE);
-
 	ACPI_DEBUG_PRINT((ACPI_DB_LOAD,
 			  "Registered GPE method %s as GPE number 0x%.2X\n",
 			  name, gpe_number));
@@ -454,20 +448,7 @@ acpi_ev_match_prw_and_gpe(acpi_handle ob
 							gpe_block->
 							block_base_number];
 
-		/* Mark GPE for WAKE-ONLY but WAKE_DISABLED */
-
-		gpe_event_info->flags &=
-		    ~(ACPI_GPE_WAKE_ENABLED | ACPI_GPE_RUN_ENABLED);
-
-		status =
-		    acpi_ev_set_gpe_type(gpe_event_info, ACPI_GPE_TYPE_WAKE);
-		if (ACPI_FAILURE(status)) {
-			goto cleanup;
-		}
-
-		status =
-		    acpi_ev_update_gpe_enable_masks(gpe_event_info,
-						    ACPI_GPE_DISABLE);
+		gpe_event_info->flags |= ACPI_GPE_CAN_WAKE;
 	}
 
       cleanup:
@@ -1027,33 +1008,35 @@ acpi_ev_initialize_gpe_block(struct acpi
 	}
 
 	/*
-	 * Enable all GPEs in this block that have these attributes:
-	 * 1) are "runtime" or "run/wake" GPEs, and
-	 * 2) have a corresponding _Lxx or _Exx method
-	 *
-	 * Any other GPEs within this block must be enabled via the
-	 * acpi_enable_gpe() external interface.
+	 * Enable all GPEs that have a corresponding method and aren't
+	 * capable of generating wakeups. Any other GPEs within this block
+	 * must be enabled via the acpi_ref_runtime_gpe interface.
 	 */
 	wake_gpe_count = 0;
 	gpe_enabled_count = 0;
 
 	for (i = 0; i < gpe_block->register_count; i++) {
 		for (j = 0; j < 8; j++) {
+			int gpe_number = i * ACPI_GPE_REGISTER_WIDTH + j;
 
 			/* Get the info block for this particular GPE */
 
-			gpe_event_info = &gpe_block->event_info[((acpi_size) i *
-								 ACPI_GPE_REGISTER_WIDTH)
-								+ j];
-
-			if (((gpe_event_info->flags & ACPI_GPE_DISPATCH_MASK) ==
-			     ACPI_GPE_DISPATCH_METHOD) &&
-			    (gpe_event_info->flags & ACPI_GPE_TYPE_RUNTIME)) {
-				gpe_enabled_count++;
-			}
+			gpe_event_info = &gpe_block->event_info[(acpi_size)
+								gpe_number];
 
-			if (gpe_event_info->flags & ACPI_GPE_TYPE_WAKE) {
+			if (gpe_event_info->flags & ACPI_GPE_CAN_WAKE) {
 				wake_gpe_count++;
+				continue;
+			}
+
+			if (gpe_event_info->flags & ACPI_GPE_DISPATCH_METHOD) {
+				gpe_enabled_count++;
+				if (gpe_device == acpi_gbl_fadt_gpe_device)
+					status = acpi_ref_runtime_gpe(NULL,
+								      gpe_number);
+				else
+					status = acpi_ref_runtime_gpe(gpe_device,
+								      gpe_number);
 			}
 		}
 	}
@@ -1062,15 +1045,7 @@ acpi_ev_initialize_gpe_block(struct acpi
 			  "Found %u Wake, Enabled %u Runtime GPEs in this block\n",
 			  wake_gpe_count, gpe_enabled_count));
 
-	/* Enable all valid runtime GPEs found above */
-
-	status = acpi_hw_enable_runtime_gpe_block(NULL, gpe_block, NULL);
-	if (ACPI_FAILURE(status)) {
-		ACPI_ERROR((AE_INFO, "Could not enable GPEs in GpeBlock %p",
-			    gpe_block));
-	}
-
-	return_ACPI_STATUS(status);
+	return_ACPI_STATUS(AE_OK);
 }
 
 /*******************************************************************************
Index: linux-2.6/drivers/acpi/acpica/evxface.c
===================================================================
--- linux-2.6.orig/drivers/acpi/acpica/evxface.c
+++ linux-2.6/drivers/acpi/acpica/evxface.c
@@ -617,13 +617,6 @@ acpi_install_gpe_handler(acpi_handle gpe
 	handler->context = context;
 	handler->method_node = gpe_event_info->dispatch.method_node;
 
-	/* Disable the GPE before installing the handler */
-
-	status = acpi_ev_disable_gpe(gpe_event_info);
-	if (ACPI_FAILURE(status)) {
-		goto unlock_and_exit;
-	}
-
 	/* Install the handler */
 
 	flags = acpi_os_acquire_lock(acpi_gbl_gpe_lock);
@@ -707,13 +700,6 @@ acpi_remove_gpe_handler(acpi_handle gpe_
 		goto unlock_and_exit;
 	}
 
-	/* Disable the GPE before removing the handler */
-
-	status = acpi_ev_disable_gpe(gpe_event_info);
-	if (ACPI_FAILURE(status)) {
-		goto unlock_and_exit;
-	}
-
 	/* Make sure all deferred tasks are completed */
 
 	(void)acpi_ut_release_mutex(ACPI_MTX_EVENTS);
Index: linux-2.6/drivers/acpi/acpica/evxfevnt.c
===================================================================
--- linux-2.6.orig/drivers/acpi/acpica/evxfevnt.c
+++ linux-2.6/drivers/acpi/acpica/evxfevnt.c
@@ -311,8 +311,7 @@ acpi_status acpi_ref_wakeup_gpe(acpi_han
 	}
 
 	if (++gpe_event_info->wakeup_count == 1)
-		acpi_ev_update_gpe_enable_masks(gpe_event_info,
-						ACPI_GPE_ENABLE);
+		acpi_ev_update_gpe_enable_masks(gpe_event_info);
 
 unlock_and_exit:
 	acpi_os_release_lock(acpi_gbl_gpe_lock, flags);
@@ -351,8 +350,7 @@ acpi_status acpi_unref_wakeup_gpe(acpi_h
 	}
 
 	if (--gpe_event_info->wakeup_count == 0)
-		acpi_ev_update_gpe_enable_masks(gpe_event_info,
-			ACPI_GPE_DISABLE);
+		acpi_ev_update_gpe_enable_masks(gpe_event_info);
 
 unlock_and_exit:
 	acpi_os_release_lock(acpi_gbl_gpe_lock, flags);
@@ -362,48 +360,6 @@ ACPI_EXPORT_SYMBOL(acpi_unref_wakeup_gpe
 
 /*******************************************************************************
  *
- * FUNCTION:    acpi_set_gpe_type
- *
- * PARAMETERS:  gpe_device      - Parent GPE Device
- *              gpe_number      - GPE level within the GPE block
- *              Type            - New GPE type
- *
- * RETURN:      Status
- *
- * DESCRIPTION: Set the type of an individual GPE
- *
- ******************************************************************************/
-acpi_status acpi_set_gpe_type(acpi_handle gpe_device, u32 gpe_number, u8 type)
-{
-	acpi_status status = AE_OK;
-	struct acpi_gpe_event_info *gpe_event_info;
-
-	ACPI_FUNCTION_TRACE(acpi_set_gpe_type);
-
-	/* Ensure that we have a valid GPE number */
-
-	gpe_event_info = acpi_ev_get_gpe_event_info(gpe_device, gpe_number);
-	if (!gpe_event_info) {
-		status = AE_BAD_PARAMETER;
-		goto unlock_and_exit;
-	}
-
-	if ((gpe_event_info->flags & ACPI_GPE_TYPE_MASK) == type) {
-		return_ACPI_STATUS(AE_OK);
-	}
-
-	/* Set the new type (will disable GPE if currently enabled) */
-
-	status = acpi_ev_set_gpe_type(gpe_event_info, type);
-
-      unlock_and_exit:
-	return_ACPI_STATUS(status);
-}
-
-ACPI_EXPORT_SYMBOL(acpi_set_gpe_type)
-
-/*******************************************************************************
- *
  * FUNCTION:    acpi_enable_gpe
  *
  * PARAMETERS:  gpe_device      - Parent GPE Device
Index: linux-2.6/drivers/acpi/button.c
===================================================================
--- linux-2.6.orig/drivers/acpi/button.c
+++ linux-2.6/drivers/acpi/button.c
@@ -422,11 +422,6 @@ static int acpi_button_add(struct acpi_d
 
 	if (device->wakeup.flags.valid) {
 		/* Button's GPE is run-wake GPE */
-		acpi_set_gpe_type(device->wakeup.gpe_device,
-				  device->wakeup.gpe_number,
-				  ACPI_GPE_TYPE_WAKE_RUN);
-		acpi_enable_gpe(device->wakeup.gpe_device,
-				device->wakeup.gpe_number);
 		acpi_ref_runtime_gpe(device->wakeup.gpe_device,
 				     device->wakeup.gpe_number);
 		acpi_ref_wakeup_gpe(device->wakeup.gpe_device,
Index: linux-2.6/drivers/acpi/ec.c
===================================================================
--- linux-2.6.orig/drivers/acpi/ec.c
+++ linux-2.6/drivers/acpi/ec.c
@@ -754,7 +754,7 @@ static int ec_install_handlers(struct ac
 				  &acpi_ec_gpe_handler, ec);
 	if (ACPI_FAILURE(status))
 		return -ENODEV;
-	acpi_set_gpe_type(NULL, ec->gpe, ACPI_GPE_TYPE_RUNTIME);
+
 	acpi_ref_runtime_gpe(NULL, ec->gpe);
 	status = acpi_install_address_space_handler(ec->handle,
 						    ACPI_ADR_SPACE_EC,
Index: linux-2.6/drivers/acpi/wakeup.c
===================================================================
--- linux-2.6.orig/drivers/acpi/wakeup.c
+++ linux-2.6/drivers/acpi/wakeup.c
@@ -62,25 +62,12 @@ void acpi_enable_wakeup_device(u8 sleep_
 		struct acpi_device *dev =
 			container_of(node, struct acpi_device, wakeup_list);
 
-		if (!dev->wakeup.flags.valid)
+		if (!dev->wakeup.flags.valid ||
+		    !dev->wakeup.prepare_count ||
+		    !dev->wakeup.state.enabled ||
+		    (sleep_state > (u32) dev->wakeup.sleep_state))
 			continue;
 
-		/* If users want to disable run-wake GPE,
-		 * we only disable it for wake and leave it for runtime
-		 */
-		if ((!dev->wakeup.state.enabled && !dev->wakeup.prepare_count)
-		    || sleep_state > (u32) dev->wakeup.sleep_state) {
-			if (dev->wakeup.flags.run_wake) {
-				/* set_gpe_type will disable GPE, leave it like that */
-				acpi_set_gpe_type(dev->wakeup.gpe_device,
-						  dev->wakeup.gpe_number,
-						  ACPI_GPE_TYPE_RUNTIME);
-			}
-			continue;
-		}
-		if (!dev->wakeup.flags.run_wake)
-			acpi_enable_gpe(dev->wakeup.gpe_device,
-					dev->wakeup.gpe_number);
 		acpi_ref_wakeup_gpe(dev->wakeup.gpe_device,
 				    dev->wakeup.gpe_number);
 	}
@@ -99,32 +86,13 @@ void acpi_disable_wakeup_device(u8 sleep
 		struct acpi_device *dev =
 			container_of(node, struct acpi_device, wakeup_list);
 
-		if (!dev->wakeup.flags.valid)
-			continue;
-
-		if ((!dev->wakeup.state.enabled && !dev->wakeup.prepare_count)
-		    || sleep_state > (u32) dev->wakeup.sleep_state) {
-			if (dev->wakeup.flags.run_wake) {
-				acpi_set_gpe_type(dev->wakeup.gpe_device,
-						  dev->wakeup.gpe_number,
-						  ACPI_GPE_TYPE_WAKE_RUN);
-				/* Re-enable it, since set_gpe_type will disable it */
-				acpi_enable_gpe(dev->wakeup.gpe_device,
-						dev->wakeup.gpe_number);
-			}
-			continue;
-		}
+		if (dev->wakeup.state.enabled &&
+		    dev->wakeup.prepare_count &&
+		    sleep_state <= (u32) dev->wakeup.sleep_state)
+			acpi_unref_wakeup_gpe(dev->wakeup.gpe_device,
+					    dev->wakeup.gpe_number);
 
 		acpi_disable_wakeup_device_power(dev);
-		/* Never disable run-wake GPE */
-		if (!dev->wakeup.flags.run_wake) {
-			acpi_disable_gpe(dev->wakeup.gpe_device,
-					 dev->wakeup.gpe_number);
-			acpi_clear_gpe(dev->wakeup.gpe_device,
-				       dev->wakeup.gpe_number, ACPI_NOT_ISR);
-		}
-		acpi_unref_wakeup_gpe(dev->wakeup.gpe_device,
-				    dev->wakeup.gpe_number);
 	}
 }
 
@@ -140,11 +108,6 @@ int __init acpi_wakeup_device_init(void)
 		/* In case user doesn't load button driver */
 		if (!dev->wakeup.flags.run_wake || dev->wakeup.state.enabled)
 			continue;
-		acpi_set_gpe_type(dev->wakeup.gpe_device,
-				  dev->wakeup.gpe_number,
-				  ACPI_GPE_TYPE_WAKE_RUN);
-		acpi_enable_gpe(dev->wakeup.gpe_device,
-				dev->wakeup.gpe_number);
 		acpi_ref_wakeup_gpe(dev->wakeup.gpe_device,
 				    dev->wakeup.gpe_number);
 		dev->wakeup.state.enabled = 1;
Index: linux-2.6/include/acpi/actypes.h
===================================================================
--- linux-2.6.orig/include/acpi/actypes.h
+++ linux-2.6/include/acpi/actypes.h
@@ -668,41 +668,29 @@ typedef u32 acpi_event_status;
 
 /*
  * GPE info flags - Per GPE
- * +-+-+-+---+---+-+
- * |7|6|5|4:3|2:1|0|
- * +-+-+-+---+---+-+
- *  | | |  |   |  |
- *  | | |  |   |  +--- Interrupt type: Edge or Level Triggered
- *  | | |  |   +--- Type: Wake-only, Runtime-only, or wake/runtime
+ * +-+-+-+---+-+-+-+
+ * |7|6|5|4:3|2|1|0|
+ * +-+-+-+---+-+-+-+
+ *  | | |  |  | | |
+ *  | | |  |  | | +--- Interrupt type: Edge or Level Triggered
+ *  | | |  |  | +--- GPE can wake the system
+ *  | | |  |  +--- Unused 
  *  | | |  +--- Type of dispatch -- to method, handler, or none
- *  | | +--- Enabled for runtime?
- *  | +--- Enabled for wake?
+ *  | | +--- Unused
+ *  | +--- Unused
  *  +--- Unused
  */
 #define ACPI_GPE_XRUPT_TYPE_MASK        (u8) 0x01
 #define ACPI_GPE_LEVEL_TRIGGERED        (u8) 0x01
 #define ACPI_GPE_EDGE_TRIGGERED         (u8) 0x00
 
-#define ACPI_GPE_TYPE_MASK              (u8) 0x06
-#define ACPI_GPE_TYPE_WAKE_RUN          (u8) 0x06
-#define ACPI_GPE_TYPE_WAKE              (u8) 0x02
-#define ACPI_GPE_TYPE_RUNTIME           (u8) 0x04	/* Default */
+#define ACPI_GPE_CAN_WAKE		(u8) 0x02
 
 #define ACPI_GPE_DISPATCH_MASK          (u8) 0x18
 #define ACPI_GPE_DISPATCH_HANDLER       (u8) 0x08
 #define ACPI_GPE_DISPATCH_METHOD        (u8) 0x10
 #define ACPI_GPE_DISPATCH_NOT_USED      (u8) 0x00	/* Default */
 
-#define ACPI_GPE_RUN_ENABLE_MASK        (u8) 0x20
-#define ACPI_GPE_RUN_ENABLED            (u8) 0x20
-#define ACPI_GPE_RUN_DISABLED           (u8) 0x00	/* Default */
-
-#define ACPI_GPE_WAKE_ENABLE_MASK       (u8) 0x40
-#define ACPI_GPE_WAKE_ENABLED           (u8) 0x40
-#define ACPI_GPE_WAKE_DISABLED          (u8) 0x00	/* Default */
-
-#define ACPI_GPE_ENABLE_MASK            (u8) 0x60	/* Both run/wake */
-
 /*
  * Flags for GPE and Lock interfaces
  */

^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH 7/12] ACPI: Remove old GPE API and transition code entirely to new one
  2009-12-27 19:57 [PATCH 0/12] PCI run-time PM support (rev. 2) Rafael J. Wysocki
                   ` (8 preceding siblings ...)
  2009-12-27 20:03   ` Rafael J. Wysocki
@ 2009-12-27 20:04 ` Rafael J. Wysocki
  2009-12-27 20:04 ` Rafael J. Wysocki
                   ` (12 subsequent siblings)
  22 siblings, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2009-12-27 20:04 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Linux PCI, LKML, ACPI Devel Maling List, Francois Romieu,
	pm list, Matthew Garrett

From: Matthew Garrett <mjg@redhat.com>

Remove the old GPE type handling entirely, which gets rid of various quirks
like the implicit disabling with GPE type setting. This requires a small
amount of rework in order to ensure that non-wake GPEs are enabled by
default to preserve existing behaviour.

Signed-off-by: Matthew Garrett <mjg@redhat.com>
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
drivers/acpi/acpica/acevents.h |    6 +--
 drivers/acpi/acpica/acevents.h |    6 -
 drivers/acpi/acpica/evgpe.c    |  147 +++--------------------------------------
 drivers/acpi/acpica/evgpeblk.c |   69 ++++++-------------
 drivers/acpi/acpica/evxface.c  |   14 ---
 drivers/acpi/acpica/evxfevnt.c |   48 -------------
 drivers/acpi/button.c          |    5 -
 drivers/acpi/ec.c              |    2 
 drivers/acpi/wakeup.c          |   55 ++-------------
 include/acpi/actypes.h         |   32 ++------
 9 files changed, 59 insertions(+), 319 deletions(-)

Index: linux-2.6/drivers/acpi/acpica/acevents.h
===================================================================
--- linux-2.6.orig/drivers/acpi/acpica/acevents.h
+++ linux-2.6/drivers/acpi/acpica/acevents.h
@@ -76,8 +76,7 @@ acpi_ev_queue_notify_request(struct acpi
  * evgpe - GPE handling and dispatch
  */
 acpi_status
-acpi_ev_update_gpe_enable_masks(struct acpi_gpe_event_info *gpe_event_info,
-				u8 type);
+acpi_ev_update_gpe_enable_masks(struct acpi_gpe_event_info *gpe_event_info);
 
 acpi_status
 acpi_ev_enable_gpe(struct acpi_gpe_event_info *gpe_event_info,
@@ -122,9 +121,6 @@ acpi_ev_gpe_dispatch(struct acpi_gpe_eve
 u32 acpi_ev_gpe_detect(struct acpi_gpe_xrupt_info *gpe_xrupt_list);
 
 acpi_status
-acpi_ev_set_gpe_type(struct acpi_gpe_event_info *gpe_event_info, u8 type);
-
-acpi_status
 acpi_ev_check_for_wake_only_gpe(struct acpi_gpe_event_info *gpe_event_info);
 
 acpi_status acpi_ev_gpe_initialize(void);
Index: linux-2.6/drivers/acpi/acpica/evgpe.c
===================================================================
--- linux-2.6.orig/drivers/acpi/acpica/evgpe.c
+++ linux-2.6/drivers/acpi/acpica/evgpe.c
@@ -54,54 +54,9 @@ static void ACPI_SYSTEM_XFACE acpi_ev_as
 
 /*******************************************************************************
  *
- * FUNCTION:    acpi_ev_set_gpe_type
- *
- * PARAMETERS:  gpe_event_info          - GPE to set
- *              Type                    - New type
- *
- * RETURN:      Status
- *
- * DESCRIPTION: Sets the new type for the GPE (wake, run, or wake/run)
- *
- ******************************************************************************/
-
-acpi_status
-acpi_ev_set_gpe_type(struct acpi_gpe_event_info *gpe_event_info, u8 type)
-{
-	acpi_status status;
-
-	ACPI_FUNCTION_TRACE(ev_set_gpe_type);
-
-	/* Validate type and update register enable masks */
-
-	switch (type) {
-	case ACPI_GPE_TYPE_WAKE:
-	case ACPI_GPE_TYPE_RUNTIME:
-	case ACPI_GPE_TYPE_WAKE_RUN:
-		break;
-
-	default:
-		return_ACPI_STATUS(AE_BAD_PARAMETER);
-	}
-
-	/* Disable the GPE if currently enabled */
-
-	status = acpi_ev_disable_gpe(gpe_event_info);
-
-	/* Clear the type bits and insert the new Type */
-
-	gpe_event_info->flags &= ~ACPI_GPE_TYPE_MASK;
-	gpe_event_info->flags |= type;
-	return_ACPI_STATUS(status);
-}
-
-/*******************************************************************************
- *
  * FUNCTION:    acpi_ev_update_gpe_enable_masks
  *
  * PARAMETERS:  gpe_event_info          - GPE to update
- *              Type                    - What to do: ACPI_GPE_DISABLE or
- *                                        ACPI_GPE_ENABLE
  *
  * RETURN:      Status
  *
@@ -110,8 +65,7 @@ acpi_ev_set_gpe_type(struct acpi_gpe_eve
  ******************************************************************************/
 
 acpi_status
-acpi_ev_update_gpe_enable_masks(struct acpi_gpe_event_info *gpe_event_info,
-				u8 type)
+acpi_ev_update_gpe_enable_masks(struct acpi_gpe_event_info *gpe_event_info)
 {
 	struct acpi_gpe_register_info *gpe_register_info;
 	u8 register_bit;
@@ -127,37 +81,13 @@ acpi_ev_update_gpe_enable_masks(struct a
 	    (1 <<
 	     (gpe_event_info->gpe_number - gpe_register_info->base_gpe_number));
 
-	/* 1) Disable case. Simply clear all enable bits */
-
-	if (type == ACPI_GPE_DISABLE) {
-		ACPI_CLEAR_BIT(gpe_register_info->enable_for_wake,
-			       register_bit);
-		ACPI_CLEAR_BIT(gpe_register_info->enable_for_run, register_bit);
-		return_ACPI_STATUS(AE_OK);
-	}
-
-	/* 2) Enable case. Set/Clear the appropriate enable bits */
-
-	switch (gpe_event_info->flags & ACPI_GPE_TYPE_MASK) {
-	case ACPI_GPE_TYPE_WAKE:
-		ACPI_SET_BIT(gpe_register_info->enable_for_wake, register_bit);
-		ACPI_CLEAR_BIT(gpe_register_info->enable_for_run, register_bit);
-		break;
+	ACPI_CLEAR_BIT(gpe_register_info->enable_for_wake, register_bit);
+	ACPI_CLEAR_BIT(gpe_register_info->enable_for_run, register_bit);
 
-	case ACPI_GPE_TYPE_RUNTIME:
-		ACPI_CLEAR_BIT(gpe_register_info->enable_for_wake,
-			       register_bit);
+	if (gpe_event_info->runtime_count)
 		ACPI_SET_BIT(gpe_register_info->enable_for_run, register_bit);
-		break;
-
-	case ACPI_GPE_TYPE_WAKE_RUN:
+	if (gpe_event_info->wakeup_count)
 		ACPI_SET_BIT(gpe_register_info->enable_for_wake, register_bit);
-		ACPI_SET_BIT(gpe_register_info->enable_for_run, register_bit);
-		break;
-
-	default:
-		return_ACPI_STATUS(AE_BAD_PARAMETER);
-	}
 
 	return_ACPI_STATUS(AE_OK);
 }
@@ -186,47 +116,21 @@ acpi_ev_enable_gpe(struct acpi_gpe_event
 
 	/* Make sure HW enable masks are updated */
 
-	status =
-	    acpi_ev_update_gpe_enable_masks(gpe_event_info, ACPI_GPE_ENABLE);
+	status = acpi_ev_update_gpe_enable_masks(gpe_event_info);
 	if (ACPI_FAILURE(status)) {
 		return_ACPI_STATUS(status);
 	}
 
 	/* Mark wake-enabled or HW enable, or both */
 
-	switch (gpe_event_info->flags & ACPI_GPE_TYPE_MASK) {
-	case ACPI_GPE_TYPE_WAKE:
-
-		ACPI_SET_BIT(gpe_event_info->flags, ACPI_GPE_WAKE_ENABLED);
-		break;
-
-	case ACPI_GPE_TYPE_WAKE_RUN:
-
-		ACPI_SET_BIT(gpe_event_info->flags, ACPI_GPE_WAKE_ENABLED);
-
-		/*lint -fallthrough */
-
-	case ACPI_GPE_TYPE_RUNTIME:
-
-		ACPI_SET_BIT(gpe_event_info->flags, ACPI_GPE_RUN_ENABLED);
-
-		if (write_to_hardware) {
-
-			/* Clear the GPE (of stale events), then enable it */
-
-			status = acpi_hw_clear_gpe(gpe_event_info);
-			if (ACPI_FAILURE(status)) {
-				return_ACPI_STATUS(status);
-			}
-
-			/* Enable the requested runtime GPE */
-
-			status = acpi_hw_write_gpe_enable_reg(gpe_event_info);
-		}
-		break;
+	if (gpe_event_info->runtime_count && write_to_hardware) {
+		/* Clear the GPE (of stale events), then enable it */
+		status = acpi_hw_clear_gpe(gpe_event_info);
+		if (ACPI_FAILURE(status))
+			return_ACPI_STATUS(status);
 
-	default:
-		return_ACPI_STATUS(AE_BAD_PARAMETER);
+		/* Enable the requested runtime GPE */
+		status = acpi_hw_write_gpe_enable_reg(gpe_event_info);
 	}
 
 	return_ACPI_STATUS(AE_OK);
@@ -253,34 +157,11 @@ acpi_status acpi_ev_disable_gpe(struct a
 	/* Make sure HW enable masks are updated */
 
 	status =
-	    acpi_ev_update_gpe_enable_masks(gpe_event_info, ACPI_GPE_DISABLE);
+	    acpi_ev_update_gpe_enable_masks(gpe_event_info);
 	if (ACPI_FAILURE(status)) {
 		return_ACPI_STATUS(status);
 	}
 
-	/* Clear the appropriate enabled flags for this GPE */
-
-	switch (gpe_event_info->flags & ACPI_GPE_TYPE_MASK) {
-	case ACPI_GPE_TYPE_WAKE:
-		ACPI_CLEAR_BIT(gpe_event_info->flags, ACPI_GPE_WAKE_ENABLED);
-		break;
-
-	case ACPI_GPE_TYPE_WAKE_RUN:
-		ACPI_CLEAR_BIT(gpe_event_info->flags, ACPI_GPE_WAKE_ENABLED);
-
-		/* fallthrough */
-
-	case ACPI_GPE_TYPE_RUNTIME:
-
-		/* Disable the requested runtime GPE */
-
-		ACPI_CLEAR_BIT(gpe_event_info->flags, ACPI_GPE_RUN_ENABLED);
-		break;
-
-	default:
-		break;
-	}
-
 	/*
 	 * Even if we don't know the GPE type, make sure that we always
 	 * disable it. low_disable_gpe will just clear the enable bit for this
Index: linux-2.6/drivers/acpi/acpica/evgpeblk.c
===================================================================
--- linux-2.6.orig/drivers/acpi/acpica/evgpeblk.c
+++ linux-2.6/drivers/acpi/acpica/evgpeblk.c
@@ -325,22 +325,16 @@ acpi_ev_save_method_info(acpi_handle obj
 
 	/*
 	 * Now we can add this information to the gpe_event_info block for use
-	 * during dispatch of this GPE. Default type is RUNTIME, although this may
-	 * change when the _PRW methods are executed later.
+	 * during dispatch of this GPE.
 	 */
 	gpe_event_info =
 	    &gpe_block->event_info[gpe_number - gpe_block->block_base_number];
 
-	gpe_event_info->flags = (u8)
-	    (type | ACPI_GPE_DISPATCH_METHOD | ACPI_GPE_TYPE_RUNTIME);
+	gpe_event_info->flags = (u8) (type | ACPI_GPE_DISPATCH_METHOD);
 
 	gpe_event_info->dispatch.method_node =
 	    (struct acpi_namespace_node *)obj_handle;
 
-	/* Update enable mask, but don't enable the HW GPE as of yet */
-
-	status = acpi_ev_enable_gpe(gpe_event_info, FALSE);
-
 	ACPI_DEBUG_PRINT((ACPI_DB_LOAD,
 			  "Registered GPE method %s as GPE number 0x%.2X\n",
 			  name, gpe_number));
@@ -454,20 +448,7 @@ acpi_ev_match_prw_and_gpe(acpi_handle ob
 							gpe_block->
 							block_base_number];
 
-		/* Mark GPE for WAKE-ONLY but WAKE_DISABLED */
-
-		gpe_event_info->flags &=
-		    ~(ACPI_GPE_WAKE_ENABLED | ACPI_GPE_RUN_ENABLED);
-
-		status =
-		    acpi_ev_set_gpe_type(gpe_event_info, ACPI_GPE_TYPE_WAKE);
-		if (ACPI_FAILURE(status)) {
-			goto cleanup;
-		}
-
-		status =
-		    acpi_ev_update_gpe_enable_masks(gpe_event_info,
-						    ACPI_GPE_DISABLE);
+		gpe_event_info->flags |= ACPI_GPE_CAN_WAKE;
 	}
 
       cleanup:
@@ -1027,33 +1008,35 @@ acpi_ev_initialize_gpe_block(struct acpi
 	}
 
 	/*
-	 * Enable all GPEs in this block that have these attributes:
-	 * 1) are "runtime" or "run/wake" GPEs, and
-	 * 2) have a corresponding _Lxx or _Exx method
-	 *
-	 * Any other GPEs within this block must be enabled via the
-	 * acpi_enable_gpe() external interface.
+	 * Enable all GPEs that have a corresponding method and aren't
+	 * capable of generating wakeups. Any other GPEs within this block
+	 * must be enabled via the acpi_ref_runtime_gpe interface.
 	 */
 	wake_gpe_count = 0;
 	gpe_enabled_count = 0;
 
 	for (i = 0; i < gpe_block->register_count; i++) {
 		for (j = 0; j < 8; j++) {
+			int gpe_number = i * ACPI_GPE_REGISTER_WIDTH + j;
 
 			/* Get the info block for this particular GPE */
 
-			gpe_event_info = &gpe_block->event_info[((acpi_size) i *
-								 ACPI_GPE_REGISTER_WIDTH)
-								+ j];
-
-			if (((gpe_event_info->flags & ACPI_GPE_DISPATCH_MASK) ==
-			     ACPI_GPE_DISPATCH_METHOD) &&
-			    (gpe_event_info->flags & ACPI_GPE_TYPE_RUNTIME)) {
-				gpe_enabled_count++;
-			}
+			gpe_event_info = &gpe_block->event_info[(acpi_size)
+								gpe_number];
 
-			if (gpe_event_info->flags & ACPI_GPE_TYPE_WAKE) {
+			if (gpe_event_info->flags & ACPI_GPE_CAN_WAKE) {
 				wake_gpe_count++;
+				continue;
+			}
+
+			if (gpe_event_info->flags & ACPI_GPE_DISPATCH_METHOD) {
+				gpe_enabled_count++;
+				if (gpe_device == acpi_gbl_fadt_gpe_device)
+					status = acpi_ref_runtime_gpe(NULL,
+								      gpe_number);
+				else
+					status = acpi_ref_runtime_gpe(gpe_device,
+								      gpe_number);
 			}
 		}
 	}
@@ -1062,15 +1045,7 @@ acpi_ev_initialize_gpe_block(struct acpi
 			  "Found %u Wake, Enabled %u Runtime GPEs in this block\n",
 			  wake_gpe_count, gpe_enabled_count));
 
-	/* Enable all valid runtime GPEs found above */
-
-	status = acpi_hw_enable_runtime_gpe_block(NULL, gpe_block, NULL);
-	if (ACPI_FAILURE(status)) {
-		ACPI_ERROR((AE_INFO, "Could not enable GPEs in GpeBlock %p",
-			    gpe_block));
-	}
-
-	return_ACPI_STATUS(status);
+	return_ACPI_STATUS(AE_OK);
 }
 
 /*******************************************************************************
Index: linux-2.6/drivers/acpi/acpica/evxface.c
===================================================================
--- linux-2.6.orig/drivers/acpi/acpica/evxface.c
+++ linux-2.6/drivers/acpi/acpica/evxface.c
@@ -617,13 +617,6 @@ acpi_install_gpe_handler(acpi_handle gpe
 	handler->context = context;
 	handler->method_node = gpe_event_info->dispatch.method_node;
 
-	/* Disable the GPE before installing the handler */
-
-	status = acpi_ev_disable_gpe(gpe_event_info);
-	if (ACPI_FAILURE(status)) {
-		goto unlock_and_exit;
-	}
-
 	/* Install the handler */
 
 	flags = acpi_os_acquire_lock(acpi_gbl_gpe_lock);
@@ -707,13 +700,6 @@ acpi_remove_gpe_handler(acpi_handle gpe_
 		goto unlock_and_exit;
 	}
 
-	/* Disable the GPE before removing the handler */
-
-	status = acpi_ev_disable_gpe(gpe_event_info);
-	if (ACPI_FAILURE(status)) {
-		goto unlock_and_exit;
-	}
-
 	/* Make sure all deferred tasks are completed */
 
 	(void)acpi_ut_release_mutex(ACPI_MTX_EVENTS);
Index: linux-2.6/drivers/acpi/acpica/evxfevnt.c
===================================================================
--- linux-2.6.orig/drivers/acpi/acpica/evxfevnt.c
+++ linux-2.6/drivers/acpi/acpica/evxfevnt.c
@@ -311,8 +311,7 @@ acpi_status acpi_ref_wakeup_gpe(acpi_han
 	}
 
 	if (++gpe_event_info->wakeup_count == 1)
-		acpi_ev_update_gpe_enable_masks(gpe_event_info,
-						ACPI_GPE_ENABLE);
+		acpi_ev_update_gpe_enable_masks(gpe_event_info);
 
 unlock_and_exit:
 	acpi_os_release_lock(acpi_gbl_gpe_lock, flags);
@@ -351,8 +350,7 @@ acpi_status acpi_unref_wakeup_gpe(acpi_h
 	}
 
 	if (--gpe_event_info->wakeup_count == 0)
-		acpi_ev_update_gpe_enable_masks(gpe_event_info,
-			ACPI_GPE_DISABLE);
+		acpi_ev_update_gpe_enable_masks(gpe_event_info);
 
 unlock_and_exit:
 	acpi_os_release_lock(acpi_gbl_gpe_lock, flags);
@@ -362,48 +360,6 @@ ACPI_EXPORT_SYMBOL(acpi_unref_wakeup_gpe
 
 /*******************************************************************************
  *
- * FUNCTION:    acpi_set_gpe_type
- *
- * PARAMETERS:  gpe_device      - Parent GPE Device
- *              gpe_number      - GPE level within the GPE block
- *              Type            - New GPE type
- *
- * RETURN:      Status
- *
- * DESCRIPTION: Set the type of an individual GPE
- *
- ******************************************************************************/
-acpi_status acpi_set_gpe_type(acpi_handle gpe_device, u32 gpe_number, u8 type)
-{
-	acpi_status status = AE_OK;
-	struct acpi_gpe_event_info *gpe_event_info;
-
-	ACPI_FUNCTION_TRACE(acpi_set_gpe_type);
-
-	/* Ensure that we have a valid GPE number */
-
-	gpe_event_info = acpi_ev_get_gpe_event_info(gpe_device, gpe_number);
-	if (!gpe_event_info) {
-		status = AE_BAD_PARAMETER;
-		goto unlock_and_exit;
-	}
-
-	if ((gpe_event_info->flags & ACPI_GPE_TYPE_MASK) == type) {
-		return_ACPI_STATUS(AE_OK);
-	}
-
-	/* Set the new type (will disable GPE if currently enabled) */
-
-	status = acpi_ev_set_gpe_type(gpe_event_info, type);
-
-      unlock_and_exit:
-	return_ACPI_STATUS(status);
-}
-
-ACPI_EXPORT_SYMBOL(acpi_set_gpe_type)
-
-/*******************************************************************************
- *
  * FUNCTION:    acpi_enable_gpe
  *
  * PARAMETERS:  gpe_device      - Parent GPE Device
Index: linux-2.6/drivers/acpi/button.c
===================================================================
--- linux-2.6.orig/drivers/acpi/button.c
+++ linux-2.6/drivers/acpi/button.c
@@ -422,11 +422,6 @@ static int acpi_button_add(struct acpi_d
 
 	if (device->wakeup.flags.valid) {
 		/* Button's GPE is run-wake GPE */
-		acpi_set_gpe_type(device->wakeup.gpe_device,
-				  device->wakeup.gpe_number,
-				  ACPI_GPE_TYPE_WAKE_RUN);
-		acpi_enable_gpe(device->wakeup.gpe_device,
-				device->wakeup.gpe_number);
 		acpi_ref_runtime_gpe(device->wakeup.gpe_device,
 				     device->wakeup.gpe_number);
 		acpi_ref_wakeup_gpe(device->wakeup.gpe_device,
Index: linux-2.6/drivers/acpi/ec.c
===================================================================
--- linux-2.6.orig/drivers/acpi/ec.c
+++ linux-2.6/drivers/acpi/ec.c
@@ -754,7 +754,7 @@ static int ec_install_handlers(struct ac
 				  &acpi_ec_gpe_handler, ec);
 	if (ACPI_FAILURE(status))
 		return -ENODEV;
-	acpi_set_gpe_type(NULL, ec->gpe, ACPI_GPE_TYPE_RUNTIME);
+
 	acpi_ref_runtime_gpe(NULL, ec->gpe);
 	status = acpi_install_address_space_handler(ec->handle,
 						    ACPI_ADR_SPACE_EC,
Index: linux-2.6/drivers/acpi/wakeup.c
===================================================================
--- linux-2.6.orig/drivers/acpi/wakeup.c
+++ linux-2.6/drivers/acpi/wakeup.c
@@ -62,25 +62,12 @@ void acpi_enable_wakeup_device(u8 sleep_
 		struct acpi_device *dev =
 			container_of(node, struct acpi_device, wakeup_list);
 
-		if (!dev->wakeup.flags.valid)
+		if (!dev->wakeup.flags.valid ||
+		    !dev->wakeup.prepare_count ||
+		    !dev->wakeup.state.enabled ||
+		    (sleep_state > (u32) dev->wakeup.sleep_state))
 			continue;
 
-		/* If users want to disable run-wake GPE,
-		 * we only disable it for wake and leave it for runtime
-		 */
-		if ((!dev->wakeup.state.enabled && !dev->wakeup.prepare_count)
-		    || sleep_state > (u32) dev->wakeup.sleep_state) {
-			if (dev->wakeup.flags.run_wake) {
-				/* set_gpe_type will disable GPE, leave it like that */
-				acpi_set_gpe_type(dev->wakeup.gpe_device,
-						  dev->wakeup.gpe_number,
-						  ACPI_GPE_TYPE_RUNTIME);
-			}
-			continue;
-		}
-		if (!dev->wakeup.flags.run_wake)
-			acpi_enable_gpe(dev->wakeup.gpe_device,
-					dev->wakeup.gpe_number);
 		acpi_ref_wakeup_gpe(dev->wakeup.gpe_device,
 				    dev->wakeup.gpe_number);
 	}
@@ -99,32 +86,13 @@ void acpi_disable_wakeup_device(u8 sleep
 		struct acpi_device *dev =
 			container_of(node, struct acpi_device, wakeup_list);
 
-		if (!dev->wakeup.flags.valid)
-			continue;
-
-		if ((!dev->wakeup.state.enabled && !dev->wakeup.prepare_count)
-		    || sleep_state > (u32) dev->wakeup.sleep_state) {
-			if (dev->wakeup.flags.run_wake) {
-				acpi_set_gpe_type(dev->wakeup.gpe_device,
-						  dev->wakeup.gpe_number,
-						  ACPI_GPE_TYPE_WAKE_RUN);
-				/* Re-enable it, since set_gpe_type will disable it */
-				acpi_enable_gpe(dev->wakeup.gpe_device,
-						dev->wakeup.gpe_number);
-			}
-			continue;
-		}
+		if (dev->wakeup.state.enabled &&
+		    dev->wakeup.prepare_count &&
+		    sleep_state <= (u32) dev->wakeup.sleep_state)
+			acpi_unref_wakeup_gpe(dev->wakeup.gpe_device,
+					    dev->wakeup.gpe_number);
 
 		acpi_disable_wakeup_device_power(dev);
-		/* Never disable run-wake GPE */
-		if (!dev->wakeup.flags.run_wake) {
-			acpi_disable_gpe(dev->wakeup.gpe_device,
-					 dev->wakeup.gpe_number);
-			acpi_clear_gpe(dev->wakeup.gpe_device,
-				       dev->wakeup.gpe_number, ACPI_NOT_ISR);
-		}
-		acpi_unref_wakeup_gpe(dev->wakeup.gpe_device,
-				    dev->wakeup.gpe_number);
 	}
 }
 
@@ -140,11 +108,6 @@ int __init acpi_wakeup_device_init(void)
 		/* In case user doesn't load button driver */
 		if (!dev->wakeup.flags.run_wake || dev->wakeup.state.enabled)
 			continue;
-		acpi_set_gpe_type(dev->wakeup.gpe_device,
-				  dev->wakeup.gpe_number,
-				  ACPI_GPE_TYPE_WAKE_RUN);
-		acpi_enable_gpe(dev->wakeup.gpe_device,
-				dev->wakeup.gpe_number);
 		acpi_ref_wakeup_gpe(dev->wakeup.gpe_device,
 				    dev->wakeup.gpe_number);
 		dev->wakeup.state.enabled = 1;
Index: linux-2.6/include/acpi/actypes.h
===================================================================
--- linux-2.6.orig/include/acpi/actypes.h
+++ linux-2.6/include/acpi/actypes.h
@@ -668,41 +668,29 @@ typedef u32 acpi_event_status;
 
 /*
  * GPE info flags - Per GPE
- * +-+-+-+---+---+-+
- * |7|6|5|4:3|2:1|0|
- * +-+-+-+---+---+-+
- *  | | |  |   |  |
- *  | | |  |   |  +--- Interrupt type: Edge or Level Triggered
- *  | | |  |   +--- Type: Wake-only, Runtime-only, or wake/runtime
+ * +-+-+-+---+-+-+-+
+ * |7|6|5|4:3|2|1|0|
+ * +-+-+-+---+-+-+-+
+ *  | | |  |  | | |
+ *  | | |  |  | | +--- Interrupt type: Edge or Level Triggered
+ *  | | |  |  | +--- GPE can wake the system
+ *  | | |  |  +--- Unused 
  *  | | |  +--- Type of dispatch -- to method, handler, or none
- *  | | +--- Enabled for runtime?
- *  | +--- Enabled for wake?
+ *  | | +--- Unused
+ *  | +--- Unused
  *  +--- Unused
  */
 #define ACPI_GPE_XRUPT_TYPE_MASK        (u8) 0x01
 #define ACPI_GPE_LEVEL_TRIGGERED        (u8) 0x01
 #define ACPI_GPE_EDGE_TRIGGERED         (u8) 0x00
 
-#define ACPI_GPE_TYPE_MASK              (u8) 0x06
-#define ACPI_GPE_TYPE_WAKE_RUN          (u8) 0x06
-#define ACPI_GPE_TYPE_WAKE              (u8) 0x02
-#define ACPI_GPE_TYPE_RUNTIME           (u8) 0x04	/* Default */
+#define ACPI_GPE_CAN_WAKE		(u8) 0x02
 
 #define ACPI_GPE_DISPATCH_MASK          (u8) 0x18
 #define ACPI_GPE_DISPATCH_HANDLER       (u8) 0x08
 #define ACPI_GPE_DISPATCH_METHOD        (u8) 0x10
 #define ACPI_GPE_DISPATCH_NOT_USED      (u8) 0x00	/* Default */
 
-#define ACPI_GPE_RUN_ENABLE_MASK        (u8) 0x20
-#define ACPI_GPE_RUN_ENABLED            (u8) 0x20
-#define ACPI_GPE_RUN_DISABLED           (u8) 0x00	/* Default */
-
-#define ACPI_GPE_WAKE_ENABLE_MASK       (u8) 0x40
-#define ACPI_GPE_WAKE_ENABLED           (u8) 0x40
-#define ACPI_GPE_WAKE_DISABLED          (u8) 0x00	/* Default */
-
-#define ACPI_GPE_ENABLE_MASK            (u8) 0x60	/* Both run/wake */
-
 /*
  * Flags for GPE and Lock interfaces
  */

^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH 8/12] ACPI / PM: Add more run-time wake-up fields
  2009-12-27 19:57 [PATCH 0/12] PCI run-time PM support (rev. 2) Rafael J. Wysocki
                   ` (10 preceding siblings ...)
  2009-12-27 20:04 ` Rafael J. Wysocki
@ 2009-12-27 20:05 ` Rafael J. Wysocki
  2010-01-08 20:39   ` Len Brown
  2010-01-08 20:39   ` Len Brown
  2009-12-27 20:05 ` Rafael J. Wysocki
                   ` (10 subsequent siblings)
  22 siblings, 2 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2009-12-27 20:05 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Matthew Garrett, Len Brown, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu

From: Rafael J. Wysocki <rjw@sisk.pl>

Use the run_wake flag to mark all devices for which run-time wake-up
events may be generated by the platform.  Introduce a new wake-up
flag, special, for marking devices that should be permanently enabled
to generate run-time events.  Also, introduce a reference counter for
run-wake devices and a function that will initialize all of the
run-time wake-up fields for given device.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
 drivers/acpi/button.c   |    2 ++
 drivers/acpi/scan.c     |   37 +++++++++++++++++++++++++++----------
 drivers/acpi/wakeup.c   |    2 +-
 include/acpi/acpi_bus.h |    2 ++
 4 files changed, 32 insertions(+), 11 deletions(-)

Index: linux-2.6/drivers/acpi/wakeup.c
===================================================================
--- linux-2.6.orig/drivers/acpi/wakeup.c
+++ linux-2.6/drivers/acpi/wakeup.c
@@ -106,7 +106,7 @@ int __init acpi_wakeup_device_init(void)
 						       struct acpi_device,
 						       wakeup_list);
 		/* In case user doesn't load button driver */
-		if (!dev->wakeup.flags.run_wake || dev->wakeup.state.enabled)
+		if (!dev->wakeup.flags.special || dev->wakeup.state.enabled)
 			continue;
 		acpi_ref_wakeup_gpe(dev->wakeup.gpe_device,
 				    dev->wakeup.gpe_number);
Index: linux-2.6/include/acpi/acpi_bus.h
===================================================================
--- linux-2.6.orig/include/acpi/acpi_bus.h
+++ linux-2.6/include/acpi/acpi_bus.h
@@ -242,6 +242,7 @@ struct acpi_device_perf {
 struct acpi_device_wakeup_flags {
 	u8 valid:1;		/* Can successfully enable wakeup? */
 	u8 run_wake:1;		/* Run-Wake GPE devices */
+	u8 special:1;		/* Run-wake devices that are always enabled */
 };
 
 struct acpi_device_wakeup_state {
@@ -256,6 +257,7 @@ struct acpi_device_wakeup {
 	struct acpi_device_wakeup_state state;
 	struct acpi_device_wakeup_flags flags;
 	int prepare_count;
+	int run_wake_count;
 };
 
 /* Device */
Index: linux-2.6/drivers/acpi/button.c
===================================================================
--- linux-2.6.orig/drivers/acpi/button.c
+++ linux-2.6/drivers/acpi/button.c
@@ -426,6 +426,7 @@ static int acpi_button_add(struct acpi_d
 				     device->wakeup.gpe_number);
 		acpi_ref_wakeup_gpe(device->wakeup.gpe_device,
 				    device->wakeup.gpe_number);
+		device->wakeup.run_wake_count++;
 		device->wakeup.state.enabled = 1;
 	}
 
@@ -450,6 +451,7 @@ static int acpi_button_remove(struct acp
 			     device->wakeup.gpe_number);
 		acpi_unref_wakeup_gpe(device->wakeup.gpe_device,
 				      device->wakeup.gpe_number);
+		device->wakeup.run_wake_count--;
 		device->wakeup.state.enabled = 0;
 	}
 
Index: linux-2.6/drivers/acpi/scan.c
===================================================================
--- linux-2.6.orig/drivers/acpi/scan.c
+++ linux-2.6/drivers/acpi/scan.c
@@ -741,19 +741,39 @@ acpi_bus_extract_wakeup_device_power_pac
 	return AE_OK;
 }
 
-static int acpi_bus_get_wakeup_device_flags(struct acpi_device *device)
+static void acpi_bus_set_run_wake_flags(struct acpi_device *device)
 {
-	acpi_status status = 0;
-	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
-	union acpi_object *package = NULL;
-	int psw_error;
-
 	struct acpi_device_id button_device_ids[] = {
 		{"PNP0C0D", 0},
 		{"PNP0C0C", 0},
 		{"PNP0C0E", 0},
 		{"", 0},
 	};
+	acpi_status status;
+	acpi_event_status event_status;
+
+	device->wakeup.run_wake_count = 0;
+
+	/* Power button, Lid switch always enable wakeup */
+	if (!acpi_match_device_ids(device, button_device_ids)) {
+		device->wakeup.flags.run_wake = 1;
+		device->wakeup.flags.special = 1;
+		return;
+	}
+
+	status = acpi_get_gpe_status(NULL, device->wakeup.gpe_number,
+					ACPI_NOT_ISR, &event_status);
+	if (status == AE_OK)
+		device->wakeup.flags.run_wake =
+				!!(event_status & ACPI_EVENT_FLAG_HANDLE);
+}
+
+static int acpi_bus_get_wakeup_device_flags(struct acpi_device *device)
+{
+	acpi_status status = 0;
+	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *package = NULL;
+	int psw_error;
 
 	/* _PRW */
 	status = acpi_evaluate_object(device->handle, "_PRW", NULL, &buffer);
@@ -773,6 +793,7 @@ static int acpi_bus_get_wakeup_device_fl
 
 	device->wakeup.flags.valid = 1;
 	device->wakeup.prepare_count = 0;
+	acpi_bus_set_run_wake_flags(device);
 	/* Call _PSW/_DSW object to disable its ability to wake the sleeping
 	 * system for the ACPI device with the _PRW object.
 	 * The _PSW object is depreciated in ACPI 3.0 and is replaced by _DSW.
@@ -784,10 +805,6 @@ static int acpi_bus_get_wakeup_device_fl
 		ACPI_DEBUG_PRINT((ACPI_DB_INFO,
 				"error in _DSW or _PSW evaluation\n"));
 
-	/* Power button, Lid switch always enable wakeup */
-	if (!acpi_match_device_ids(device, button_device_ids))
-		device->wakeup.flags.run_wake = 1;
-
 end:
 	if (ACPI_FAILURE(status))
 		device->flags.wake_capable = 0;

^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH 8/12] ACPI / PM: Add more run-time wake-up fields
  2009-12-27 19:57 [PATCH 0/12] PCI run-time PM support (rev. 2) Rafael J. Wysocki
                   ` (11 preceding siblings ...)
  2009-12-27 20:05 ` [PATCH 8/12] ACPI / PM: Add more run-time wake-up fields Rafael J. Wysocki
@ 2009-12-27 20:05 ` Rafael J. Wysocki
  2009-12-27 20:06 ` [PATCH 9/12] ACPI / PM: Introduce acpi_pm_wakeup_power() Rafael J. Wysocki
                   ` (9 subsequent siblings)
  22 siblings, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2009-12-27 20:05 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Linux PCI, LKML, ACPI Devel Maling List, Francois Romieu,
	pm list, Matthew Garrett

From: Rafael J. Wysocki <rjw@sisk.pl>

Use the run_wake flag to mark all devices for which run-time wake-up
events may be generated by the platform.  Introduce a new wake-up
flag, special, for marking devices that should be permanently enabled
to generate run-time events.  Also, introduce a reference counter for
run-wake devices and a function that will initialize all of the
run-time wake-up fields for given device.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
 drivers/acpi/button.c   |    2 ++
 drivers/acpi/scan.c     |   37 +++++++++++++++++++++++++++----------
 drivers/acpi/wakeup.c   |    2 +-
 include/acpi/acpi_bus.h |    2 ++
 4 files changed, 32 insertions(+), 11 deletions(-)

Index: linux-2.6/drivers/acpi/wakeup.c
===================================================================
--- linux-2.6.orig/drivers/acpi/wakeup.c
+++ linux-2.6/drivers/acpi/wakeup.c
@@ -106,7 +106,7 @@ int __init acpi_wakeup_device_init(void)
 						       struct acpi_device,
 						       wakeup_list);
 		/* In case user doesn't load button driver */
-		if (!dev->wakeup.flags.run_wake || dev->wakeup.state.enabled)
+		if (!dev->wakeup.flags.special || dev->wakeup.state.enabled)
 			continue;
 		acpi_ref_wakeup_gpe(dev->wakeup.gpe_device,
 				    dev->wakeup.gpe_number);
Index: linux-2.6/include/acpi/acpi_bus.h
===================================================================
--- linux-2.6.orig/include/acpi/acpi_bus.h
+++ linux-2.6/include/acpi/acpi_bus.h
@@ -242,6 +242,7 @@ struct acpi_device_perf {
 struct acpi_device_wakeup_flags {
 	u8 valid:1;		/* Can successfully enable wakeup? */
 	u8 run_wake:1;		/* Run-Wake GPE devices */
+	u8 special:1;		/* Run-wake devices that are always enabled */
 };
 
 struct acpi_device_wakeup_state {
@@ -256,6 +257,7 @@ struct acpi_device_wakeup {
 	struct acpi_device_wakeup_state state;
 	struct acpi_device_wakeup_flags flags;
 	int prepare_count;
+	int run_wake_count;
 };
 
 /* Device */
Index: linux-2.6/drivers/acpi/button.c
===================================================================
--- linux-2.6.orig/drivers/acpi/button.c
+++ linux-2.6/drivers/acpi/button.c
@@ -426,6 +426,7 @@ static int acpi_button_add(struct acpi_d
 				     device->wakeup.gpe_number);
 		acpi_ref_wakeup_gpe(device->wakeup.gpe_device,
 				    device->wakeup.gpe_number);
+		device->wakeup.run_wake_count++;
 		device->wakeup.state.enabled = 1;
 	}
 
@@ -450,6 +451,7 @@ static int acpi_button_remove(struct acp
 			     device->wakeup.gpe_number);
 		acpi_unref_wakeup_gpe(device->wakeup.gpe_device,
 				      device->wakeup.gpe_number);
+		device->wakeup.run_wake_count--;
 		device->wakeup.state.enabled = 0;
 	}
 
Index: linux-2.6/drivers/acpi/scan.c
===================================================================
--- linux-2.6.orig/drivers/acpi/scan.c
+++ linux-2.6/drivers/acpi/scan.c
@@ -741,19 +741,39 @@ acpi_bus_extract_wakeup_device_power_pac
 	return AE_OK;
 }
 
-static int acpi_bus_get_wakeup_device_flags(struct acpi_device *device)
+static void acpi_bus_set_run_wake_flags(struct acpi_device *device)
 {
-	acpi_status status = 0;
-	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
-	union acpi_object *package = NULL;
-	int psw_error;
-
 	struct acpi_device_id button_device_ids[] = {
 		{"PNP0C0D", 0},
 		{"PNP0C0C", 0},
 		{"PNP0C0E", 0},
 		{"", 0},
 	};
+	acpi_status status;
+	acpi_event_status event_status;
+
+	device->wakeup.run_wake_count = 0;
+
+	/* Power button, Lid switch always enable wakeup */
+	if (!acpi_match_device_ids(device, button_device_ids)) {
+		device->wakeup.flags.run_wake = 1;
+		device->wakeup.flags.special = 1;
+		return;
+	}
+
+	status = acpi_get_gpe_status(NULL, device->wakeup.gpe_number,
+					ACPI_NOT_ISR, &event_status);
+	if (status == AE_OK)
+		device->wakeup.flags.run_wake =
+				!!(event_status & ACPI_EVENT_FLAG_HANDLE);
+}
+
+static int acpi_bus_get_wakeup_device_flags(struct acpi_device *device)
+{
+	acpi_status status = 0;
+	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *package = NULL;
+	int psw_error;
 
 	/* _PRW */
 	status = acpi_evaluate_object(device->handle, "_PRW", NULL, &buffer);
@@ -773,6 +793,7 @@ static int acpi_bus_get_wakeup_device_fl
 
 	device->wakeup.flags.valid = 1;
 	device->wakeup.prepare_count = 0;
+	acpi_bus_set_run_wake_flags(device);
 	/* Call _PSW/_DSW object to disable its ability to wake the sleeping
 	 * system for the ACPI device with the _PRW object.
 	 * The _PSW object is depreciated in ACPI 3.0 and is replaced by _DSW.
@@ -784,10 +805,6 @@ static int acpi_bus_get_wakeup_device_fl
 		ACPI_DEBUG_PRINT((ACPI_DB_INFO,
 				"error in _DSW or _PSW evaluation\n"));
 
-	/* Power button, Lid switch always enable wakeup */
-	if (!acpi_match_device_ids(device, button_device_ids))
-		device->wakeup.flags.run_wake = 1;
-
 end:
 	if (ACPI_FAILURE(status))
 		device->flags.wake_capable = 0;

^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH 9/12] ACPI / PM: Introduce acpi_pm_wakeup_power()
  2009-12-27 19:57 [PATCH 0/12] PCI run-time PM support (rev. 2) Rafael J. Wysocki
                   ` (12 preceding siblings ...)
  2009-12-27 20:05 ` Rafael J. Wysocki
@ 2009-12-27 20:06 ` Rafael J. Wysocki
  2010-01-06 22:00   ` Jesse Barnes
  2010-01-06 22:00   ` Jesse Barnes
  2009-12-27 20:06 ` Rafael J. Wysocki
                   ` (8 subsequent siblings)
  22 siblings, 2 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2009-12-27 20:06 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Matthew Garrett, Len Brown, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu

From: Rafael J. Wysocki <rjw@sisk.pl>

PCI run-time power management generally requires us to set up wake-up
power for devices that are supposed to generate wake-up events, but
at the moment we only have acpi_pm_device_sleep_wake() allowing us
only to do that during system-wide power transitions.  For this
reason introduce another helper function, acpi_pm_wakeup_power(),
allowing us to enable/disable wake-up power for a device at any time
and rework acpi_pm_device_sleep_wake() to use it too.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
 drivers/acpi/sleep.c    |   22 ++++++++++++++++------
 include/acpi/acpi_bus.h |   11 ++++++++---
 2 files changed, 24 insertions(+), 9 deletions(-)

Index: linux-2.6/kernel/power/Kconfig
===================================================================
--- linux-2.6.orig/kernel/power/Kconfig
+++ linux-2.6/kernel/power/Kconfig
@@ -236,3 +236,8 @@ config PM_RUNTIME
 	  and the bus type drivers of the buses the devices are on are
 	  responsible for the actual handling of the autosuspend requests and
 	  wake-up events.
+
+config PM_WAKEUP
+	bool
+	depends on SUSPEND || HIBERNATION || PM_RUNTIME
+	default y
Index: linux-2.6/drivers/acpi/sleep.c
===================================================================
--- linux-2.6.orig/drivers/acpi/sleep.c
+++ linux-2.6/drivers/acpi/sleep.c
@@ -634,7 +634,7 @@ int acpi_suspend(u32 acpi_state)
 	return -EINVAL;
 }
 
-#ifdef CONFIG_PM_SLEEP
+#ifdef CONFIG_PM_WAKEUP
 /**
  *	acpi_pm_device_sleep_state - return preferred power state of ACPI device
  *		in the system sleep state given by %acpi_target_sleep_state
@@ -720,6 +720,18 @@ int acpi_pm_device_sleep_state(struct de
 }
 
 /**
+ * acpi_pm_wakeup_power - Enable/disable device wake-up power.
+ * @dev: ACPI device to handle.
+ * @enable: Whether to enable or disable the wake-up power of the device.
+ */
+int acpi_pm_wakeup_power(struct acpi_device *dev, bool enable)
+{
+	return enable ?
+		acpi_enable_wakeup_device_power(dev, acpi_target_sleep_state) :
+		acpi_disable_wakeup_device_power(dev);
+}
+
+/**
  *	acpi_pm_device_sleep_wake - enable or disable the system wake-up
  *                                  capability of given device
  *	@dev: device to handle
@@ -740,16 +752,14 @@ int acpi_pm_device_sleep_wake(struct dev
 		return -ENODEV;
 	}
 
-	error = enable ?
-		acpi_enable_wakeup_device_power(adev, acpi_target_sleep_state) :
-		acpi_disable_wakeup_device_power(adev);
+	error = acpi_pm_wakeup_power(adev, enable);
 	if (!error)
-		dev_info(dev, "wake-up capability %s by ACPI\n",
+		dev_info(dev, "wake-up power %s by ACPI\n",
 				enable ? "enabled" : "disabled");
 
 	return error;
 }
-#endif
+#endif /* CONFIG_PM_WAKEUP */
 
 static void acpi_power_off_prepare(void)
 {
Index: linux-2.6/include/acpi/acpi_bus.h
===================================================================
--- linux-2.6.orig/include/acpi/acpi_bus.h
+++ linux-2.6/include/acpi/acpi_bus.h
@@ -388,21 +388,26 @@ acpi_handle acpi_get_pci_rootbridge_hand
 struct acpi_pci_root *acpi_pci_find_root(acpi_handle handle);
 #define DEVICE_ACPI_HANDLE(dev) ((acpi_handle)((dev)->archdata.acpi_handle))
 
-#ifdef CONFIG_PM_SLEEP
+#ifdef CONFIG_PM_WAKEUP
 int acpi_pm_device_sleep_state(struct device *, int *);
+int acpi_pm_wakeup_power(struct acpi_device *, bool);
 int acpi_pm_device_sleep_wake(struct device *, bool);
-#else /* !CONFIG_PM_SLEEP */
+#else /* !CONFIG_PM_WAKEUP */
 static inline int acpi_pm_device_sleep_state(struct device *d, int *p)
 {
 	if (p)
 		*p = ACPI_STATE_D0;
 	return ACPI_STATE_D3;
 }
+static inline int acpi_pm_wakeup_power(struct acpi_device *dev, bool enable)
+{
+	return -ENODEV;
+}
 static inline int acpi_pm_device_sleep_wake(struct device *dev, bool enable)
 {
 	return -ENODEV;
 }
-#endif /* !CONFIG_PM_SLEEP */
+#endif /* !CONFIG_PM_WAKEUP */
 
 #endif				/* CONFIG_ACPI */
 


^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH 9/12] ACPI / PM: Introduce acpi_pm_wakeup_power()
  2009-12-27 19:57 [PATCH 0/12] PCI run-time PM support (rev. 2) Rafael J. Wysocki
                   ` (13 preceding siblings ...)
  2009-12-27 20:06 ` [PATCH 9/12] ACPI / PM: Introduce acpi_pm_wakeup_power() Rafael J. Wysocki
@ 2009-12-27 20:06 ` Rafael J. Wysocki
  2009-12-27 20:07 ` [PATCH 10/12] PCI / ACPI / PM: Platform support for PCI PME wake-up (rev. 6) Rafael J. Wysocki
                   ` (7 subsequent siblings)
  22 siblings, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2009-12-27 20:06 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Linux PCI, LKML, ACPI Devel Maling List, Francois Romieu,
	pm list, Matthew Garrett

From: Rafael J. Wysocki <rjw@sisk.pl>

PCI run-time power management generally requires us to set up wake-up
power for devices that are supposed to generate wake-up events, but
at the moment we only have acpi_pm_device_sleep_wake() allowing us
only to do that during system-wide power transitions.  For this
reason introduce another helper function, acpi_pm_wakeup_power(),
allowing us to enable/disable wake-up power for a device at any time
and rework acpi_pm_device_sleep_wake() to use it too.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
 drivers/acpi/sleep.c    |   22 ++++++++++++++++------
 include/acpi/acpi_bus.h |   11 ++++++++---
 2 files changed, 24 insertions(+), 9 deletions(-)

Index: linux-2.6/kernel/power/Kconfig
===================================================================
--- linux-2.6.orig/kernel/power/Kconfig
+++ linux-2.6/kernel/power/Kconfig
@@ -236,3 +236,8 @@ config PM_RUNTIME
 	  and the bus type drivers of the buses the devices are on are
 	  responsible for the actual handling of the autosuspend requests and
 	  wake-up events.
+
+config PM_WAKEUP
+	bool
+	depends on SUSPEND || HIBERNATION || PM_RUNTIME
+	default y
Index: linux-2.6/drivers/acpi/sleep.c
===================================================================
--- linux-2.6.orig/drivers/acpi/sleep.c
+++ linux-2.6/drivers/acpi/sleep.c
@@ -634,7 +634,7 @@ int acpi_suspend(u32 acpi_state)
 	return -EINVAL;
 }
 
-#ifdef CONFIG_PM_SLEEP
+#ifdef CONFIG_PM_WAKEUP
 /**
  *	acpi_pm_device_sleep_state - return preferred power state of ACPI device
  *		in the system sleep state given by %acpi_target_sleep_state
@@ -720,6 +720,18 @@ int acpi_pm_device_sleep_state(struct de
 }
 
 /**
+ * acpi_pm_wakeup_power - Enable/disable device wake-up power.
+ * @dev: ACPI device to handle.
+ * @enable: Whether to enable or disable the wake-up power of the device.
+ */
+int acpi_pm_wakeup_power(struct acpi_device *dev, bool enable)
+{
+	return enable ?
+		acpi_enable_wakeup_device_power(dev, acpi_target_sleep_state) :
+		acpi_disable_wakeup_device_power(dev);
+}
+
+/**
  *	acpi_pm_device_sleep_wake - enable or disable the system wake-up
  *                                  capability of given device
  *	@dev: device to handle
@@ -740,16 +752,14 @@ int acpi_pm_device_sleep_wake(struct dev
 		return -ENODEV;
 	}
 
-	error = enable ?
-		acpi_enable_wakeup_device_power(adev, acpi_target_sleep_state) :
-		acpi_disable_wakeup_device_power(adev);
+	error = acpi_pm_wakeup_power(adev, enable);
 	if (!error)
-		dev_info(dev, "wake-up capability %s by ACPI\n",
+		dev_info(dev, "wake-up power %s by ACPI\n",
 				enable ? "enabled" : "disabled");
 
 	return error;
 }
-#endif
+#endif /* CONFIG_PM_WAKEUP */
 
 static void acpi_power_off_prepare(void)
 {
Index: linux-2.6/include/acpi/acpi_bus.h
===================================================================
--- linux-2.6.orig/include/acpi/acpi_bus.h
+++ linux-2.6/include/acpi/acpi_bus.h
@@ -388,21 +388,26 @@ acpi_handle acpi_get_pci_rootbridge_hand
 struct acpi_pci_root *acpi_pci_find_root(acpi_handle handle);
 #define DEVICE_ACPI_HANDLE(dev) ((acpi_handle)((dev)->archdata.acpi_handle))
 
-#ifdef CONFIG_PM_SLEEP
+#ifdef CONFIG_PM_WAKEUP
 int acpi_pm_device_sleep_state(struct device *, int *);
+int acpi_pm_wakeup_power(struct acpi_device *, bool);
 int acpi_pm_device_sleep_wake(struct device *, bool);
-#else /* !CONFIG_PM_SLEEP */
+#else /* !CONFIG_PM_WAKEUP */
 static inline int acpi_pm_device_sleep_state(struct device *d, int *p)
 {
 	if (p)
 		*p = ACPI_STATE_D0;
 	return ACPI_STATE_D3;
 }
+static inline int acpi_pm_wakeup_power(struct acpi_device *dev, bool enable)
+{
+	return -ENODEV;
+}
 static inline int acpi_pm_device_sleep_wake(struct device *dev, bool enable)
 {
 	return -ENODEV;
 }
-#endif /* !CONFIG_PM_SLEEP */
+#endif /* !CONFIG_PM_WAKEUP */
 
 #endif				/* CONFIG_ACPI */
 

^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH 10/12] PCI / ACPI / PM: Platform support for PCI PME wake-up (rev. 6)
  2009-12-27 19:57 [PATCH 0/12] PCI run-time PM support (rev. 2) Rafael J. Wysocki
                   ` (14 preceding siblings ...)
  2009-12-27 20:06 ` Rafael J. Wysocki
@ 2009-12-27 20:07 ` Rafael J. Wysocki
  2010-01-06 22:04   ` Jesse Barnes
  2010-01-06 22:04   ` Jesse Barnes
  2009-12-27 20:07 ` Rafael J. Wysocki
                   ` (6 subsequent siblings)
  22 siblings, 2 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2009-12-27 20:07 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Matthew Garrett, Len Brown, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu

From: Rafael J. Wysocki <rjw@sisk.pl>

Although the majority of PCI devices can generate PMEs that in
principle may be used to wake up devices suspended at run time,
platform support is generally necessary to convert PMEs into wake-up
events that can be delivered to the kernel.  If ACPI is used for this
purpose, a PME generated by a PCI device will trigger the ACPI GPE
associated with the device to generate an ACPI wake-up event that we
can set up a handler for, provided that everything is configured
correctly.

Unfortunately, the subset of PCI devices that have GPEs associated
with them is quite limited and the other devices have to rely on
the GPEs associated with their upstream bridges and, possibly, the
root bridge to generate ACPI wake-up events in response to PMEs from
them.  Moreover, ACPI-based PCI hotplug also uses ACPI notify
handlers that in general may conflict with the PM notify handlers,
unless this issue is specifically taken care of.

Add ACPI platform support for PCI PME wake-up:
o Add a framework making is possible to use ACPI system notify
  handlers for both PM and hotplug at the same time and to take the
  wake-up GPE sharing into account.
o Add new PCI platform callback ->run_wake() to struct
  pci_platform_pm_ops allowing us to enable/disable the platform to
  generate wake-up events for given device.  Implemet this callback
  for the ACPI platform.
o Define ACPI wake-up handlers for PCI devices and PCI buses and make
  the PCI-ACPI binding code register wake-up notifiers for devices
  associated with wake-up GPEs.
o Add function pci_dev_run_wake() which can be used by PCI drivers to
  check if given device is capable of generating wake-up events at
  run time.

Developed in cooperation with Matthew Garrett <mjg@redhat.com>.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
 drivers/acpi/pci_bind.c            |   14 +
 drivers/acpi/pci_root.c            |    8 
 drivers/pci/hotplug/acpiphp_glue.c |   23 --
 drivers/pci/pci-acpi.c             |  341 +++++++++++++++++++++++++++++++++++++
 drivers/pci/pci.c                  |   67 +++++++
 drivers/pci/pci.h                  |    7 
 include/acpi/acpi_bus.h            |    1 
 include/linux/pci-acpi.h           |   10 +
 include/linux/pci.h                |    1 
 9 files changed, 457 insertions(+), 15 deletions(-)

Index: linux-2.6/drivers/pci/pci.h
===================================================================
--- linux-2.6.orig/drivers/pci/pci.h
+++ linux-2.6/drivers/pci/pci.h
@@ -35,6 +35,10 @@ int pci_probe_reset_function(struct pci_
  *
  * @sleep_wake: enables/disables the system wake up capability of given device
  *
+ * @run_wake: enables/disables the platform to generate run-time wake-up events
+ *		for given device (the device's wake-up capability has to be
+ *		enabled by @sleep_wake for this feature to work)
+ *
  * If given platform is generally capable of power managing PCI devices, all of
  * these callbacks are mandatory.
  */
@@ -44,12 +48,15 @@ struct pci_platform_pm_ops {
 	pci_power_t (*choose_state)(struct pci_dev *dev);
 	bool (*can_wakeup)(struct pci_dev *dev);
 	int (*sleep_wake)(struct pci_dev *dev, bool enable);
+	int (*run_wake)(struct pci_dev *dev, bool enable);
 };
 
 extern int pci_set_platform_pm(struct pci_platform_pm_ops *ops);
 extern void pci_update_current_state(struct pci_dev *dev, pci_power_t state);
 extern void pci_disable_enabled_device(struct pci_dev *dev);
 extern bool pci_check_pme_status(struct pci_dev *dev);
+extern int __pci_pme_wakeup(struct pci_dev *dev, void *ign);
+extern void pci_pme_wakeup_bus(struct pci_bus *bus);
 extern void pci_pm_init(struct pci_dev *dev);
 extern void platform_pci_wakeup_init(struct pci_dev *dev);
 extern void pci_allocate_cap_save_buffers(struct pci_dev *dev);
Index: linux-2.6/drivers/pci/pci-acpi.c
===================================================================
--- linux-2.6.orig/drivers/pci/pci-acpi.c
+++ linux-2.6/drivers/pci/pci-acpi.c
@@ -16,8 +16,276 @@
 #include <acpi/acpi_bus.h>
 
 #include <linux/pci-acpi.h>
+#include <linux/pm_runtime.h>
 #include "pci.h"
 
+static DEFINE_MUTEX(pci_acpi_notifier_mtx);
+
+struct pci_acpi_notify_data
+{
+	acpi_notify_handler hp_cb;
+	void *hp_data;
+	struct pci_bus *pci_bus;
+	struct pci_dev *pci_dev;
+};
+
+/**
+ * pci_acpi_event_fn - Universal system notification handler.
+ * @handle: ACPI handle of a device the notification is for.
+ * @event: Type of the signaled event.
+ * @ign: The value of this argument is ignored.
+ *
+ * Use @handle to obtain the address of the ACPI device object the event is
+ * signaled for.  If this is a wake-up event, execute the appropriate PME
+ * handler for the bus or device represented by it (or both, if @dev is a
+ * bridge).  If this is not a wake-up event, execute the hotplug notify handler
+ * for @handle.
+ */
+static void pci_acpi_event_fn(acpi_handle handle, u32 event, void *ign)
+{
+	struct acpi_device *dev;
+	struct pci_acpi_notify_data *nd;
+
+	if (ACPI_FAILURE(acpi_bus_get_device(handle, &dev))) {
+		pr_warning("ACPI handle has no context in %s!\n", __func__);
+		return;
+	}
+
+	mutex_lock(&pci_acpi_notifier_mtx);
+
+	nd = dev->bus_data;
+	if (!nd)
+		goto out;
+
+	if (event == ACPI_NOTIFY_DEVICE_WAKE) {
+		if (nd->pci_bus) {
+			pci_pme_wakeup_bus(nd->pci_bus);
+		}
+		if (nd->pci_dev) {
+			pci_check_pme_status(nd->pci_dev);
+			pm_request_resume(&nd->pci_dev->dev);
+		}
+	} else if (nd->hp_cb) {
+		nd->hp_cb(handle, event, nd->hp_data);
+	}
+
+ out:
+	mutex_unlock(&pci_acpi_notifier_mtx);
+}
+
+/**
+ * add_notify_data - Create a new notify data object for given ACPI device.
+ * @dev: Device to create the notify data object for.
+ */
+static struct pci_acpi_notify_data *add_notify_data(struct acpi_device *dev)
+{
+	struct pci_acpi_notify_data *nd;
+
+	nd = kzalloc(sizeof(*nd), GFP_KERNEL);
+	if (!nd)
+		return NULL;
+
+	dev->bus_data = nd;
+	return nd;
+}
+
+/**
+ * remove_notify_data - Remove the notify data object from given ACPI device.
+ * @dev: Device to remove the notify data object from.
+ */
+static void remove_notify_data(struct acpi_device *dev)
+{
+	kfree(dev->bus_data);
+	dev->bus_data = NULL;
+}
+
+/**
+ * pci_acpi_add_hp_notifier - Register a hotplug notifier for given device.
+ * @handle: ACPI handle of the device to register the notifier for.
+ * @handler: Callback to execute for hotplug events related to @handle.
+ * @context: Pointer to the context data to pass to @handler.
+ *
+ * Use @handle to get an ACPI device object and check if there is a notify data
+ * object for it.  If this is the case, add @handler and @context to the
+ * existing notify data object, unless there already is a hotplug handler in
+ * there.  Otherwise, create a new notify data object for the ACPI device
+ * associated with @handle and add @handler and @context to it.
+ */
+acpi_status pci_acpi_add_hp_notifier(acpi_handle handle,
+				     acpi_notify_handler handler, void *context)
+{
+	struct pci_acpi_notify_data *nd;
+	struct acpi_device *dev;
+	acpi_status status = AE_OK;
+
+	if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &dev)))
+		return AE_NOT_FOUND;
+
+	mutex_lock(&pci_acpi_notifier_mtx);
+
+	nd = dev->bus_data;
+	if (nd) {
+		if (!nd->hp_cb)
+			goto add;
+
+		status = AE_ALREADY_EXISTS;
+		goto out;
+	}
+
+	nd = add_notify_data(dev);
+	if (!nd) {
+		status = AE_NO_MEMORY;
+		goto out;
+	}
+
+	status = acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
+						pci_acpi_event_fn, NULL);
+	if (ACPI_FAILURE(status)) {
+		remove_notify_data(dev);
+		goto out;
+	}
+
+ add:
+	nd->hp_cb = handler;
+	nd->hp_data = context;
+
+ out:
+	mutex_unlock(&pci_acpi_notifier_mtx);
+
+	return status;
+}
+EXPORT_SYMBOL_GPL(pci_acpi_add_hp_notifier);
+
+/**
+ * pci_acpi_remove_hp_notifier - Unregister a hotplug notifier for given device.
+ * @handle: ACPI handle of the device to unregister the notifier for.
+ * @handler: Callback executed for hotplug events related to @handle.
+ *
+ * Remove the hotplug callback and the pointer to the hotplug context data from
+ * the notify data object belonging to the device represented by @handle.  If
+ * the notify data object is not necessary any more, remove it altogether.
+ */
+acpi_status pci_acpi_remove_hp_notifier(acpi_handle handle,
+					acpi_notify_handler handler)
+{
+	struct pci_acpi_notify_data *nd;
+	struct acpi_device *dev;
+	acpi_status status = AE_NOT_FOUND;
+
+	if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &dev)))
+		return AE_NOT_FOUND;
+
+	mutex_lock(&pci_acpi_notifier_mtx);
+
+	nd = dev->bus_data;
+	if (!nd)
+		goto out;
+
+	nd->hp_data = NULL;
+	nd->hp_cb = NULL;
+
+	if (nd->pci_bus || nd->pci_dev)
+		goto out;
+
+	status = acpi_remove_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
+						pci_acpi_event_fn);
+	remove_notify_data(dev);
+
+ out:
+	mutex_unlock(&pci_acpi_notifier_mtx);
+	return status;
+}
+EXPORT_SYMBOL_GPL(pci_acpi_remove_hp_notifier);
+
+/**
+ * pci_acpi_add_pm_notifier - Register PM notifier for given device.
+ * @dev: ACPI device to add the notifier for.
+ * @pci_dev: PCI device to check for the PME status if an event is signaled.
+ * @pci_bus: PCI bus to walk (checking PME status) if an event is signaled.
+ *
+ * Check if there is a notify data object for @dev and if that is the case, add
+ * @pci_dev to it as the device whose PME status should be checked whenever a PM
+ * event is signaled for @dev.  Also, add @pci_bus to it as the bus to walk
+ * checking the PME status of all devices on it whenever a PM event is signaled
+ * for @dev.
+ *
+ * Otherwise, create a new notify data object for @dev and add both @pci_dev and
+ * @pci_bus to it.
+ *
+ * NOTE: @dev need not be a run-wake or wake-up device to be a valid source of
+ * PM wake-up events.  For example, wake-up events may be generated for bridges
+ * if one of the devices below the bridge is signaling PME, even if the bridge
+ * itself doesn't have a wake-up GPE associated with it.
+ */
+acpi_status pci_acpi_add_pm_notifier(struct acpi_device *dev,
+				     struct pci_dev *pci_dev,
+				     struct pci_bus *pci_bus)
+{
+	struct pci_acpi_notify_data *nd;
+	acpi_status status = AE_OK;
+
+	mutex_lock(&pci_acpi_notifier_mtx);
+
+	nd = dev->bus_data;
+	if (nd)
+		goto add;
+
+	nd = add_notify_data(dev);
+	if (!nd) {
+		status = AE_NO_MEMORY;
+		goto out;
+	}
+
+	status = acpi_install_notify_handler(dev->handle, ACPI_SYSTEM_NOTIFY,
+						pci_acpi_event_fn, NULL);
+	if (ACPI_FAILURE(status)) {
+		remove_notify_data(dev);
+		goto out;
+	}
+
+ add:
+	nd->pci_dev = pci_dev;
+	nd->pci_bus = pci_bus;
+
+ out:
+	mutex_unlock(&pci_acpi_notifier_mtx);
+	return status;
+}
+
+/**
+ * pci_acpi_remove_pm_notifier - Unregister PM notifier for given device.
+ * @dev: ACPI device to remove the notifier from.
+ *
+ * Find the notify data object for @dev and clear its @pci_dev and @pci_bus
+ * fields.  If the notify data object is not necessary any more after that,
+ * remove it too.
+ */
+acpi_status pci_acpi_remove_pm_notifier(struct acpi_device *dev)
+{
+	struct pci_acpi_notify_data *nd;
+	acpi_status status = AE_NOT_FOUND;
+
+	mutex_lock(&pci_acpi_notifier_mtx);
+
+	nd = dev->bus_data;
+	if (!nd)
+		goto out;
+
+	nd->pci_dev = NULL;
+	nd->pci_bus = NULL;
+
+	if (nd->hp_cb)
+		goto out;
+
+	status = acpi_remove_notify_handler(dev->handle, ACPI_SYSTEM_NOTIFY,
+						pci_acpi_event_fn);
+	remove_notify_data(dev);
+
+ out:
+	mutex_unlock(&pci_acpi_notifier_mtx);
+	return status;
+}
+
 /*
  * _SxD returns the D-state with the highest power
  * (lowest D-state number) supported in the S-state "x".
@@ -131,12 +399,85 @@ static int acpi_pci_sleep_wake(struct pc
 	return 0;
 }
 
+/**
+ * acpi_dev_run_wake_enable - Enable/disable wake-up for given device.
+ * @phys_dev: Device to enable/disable the platform to wake-up the system for.
+ * @enable: Whether enable or disable the wake-up functionality.
+ *
+ * Find the ACPI device object corresponding to @pci_dev and try to
+ * enable/disable the GPE associated with it.
+ */
+static int acpi_dev_run_wake_enable(struct device *phys_dev, bool enable)
+{
+	struct acpi_device *dev;
+	acpi_handle handle;
+	int error = -ENODEV;
+
+	if (!device_run_wake(phys_dev))
+		return -EINVAL;
+
+	handle = DEVICE_ACPI_HANDLE(phys_dev);
+	if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &dev))) {
+		dev_dbg(phys_dev, "ACPI handle has no context in %s!\n",
+			__func__);
+		return -ENODEV;
+	}
+
+	if (enable) {
+		if (!dev->wakeup.run_wake_count++) {
+			acpi_pm_wakeup_power(dev, true);
+			acpi_ref_runtime_gpe(dev->wakeup.gpe_device,
+						dev->wakeup.gpe_number);
+		}
+	} else if (dev->wakeup.run_wake_count > 0) {
+		if (!--dev->wakeup.run_wake_count) {
+			acpi_unref_runtime_gpe(dev->wakeup.gpe_device,
+						dev->wakeup.gpe_number);
+			acpi_pm_wakeup_power(dev, false);
+		}
+	} else {
+		error = -EALREADY;
+	}
+
+	return error;
+}
+
+static void acpi_pci_propagate_run_wake(struct pci_bus *bus, bool enable)
+{
+	while (bus->parent) {
+		struct pci_dev *bridge = bus->self;
+
+		if (bridge->pme_interrupt)
+			return;
+		if (!acpi_dev_run_wake_enable(&bridge->dev, enable))
+			return;
+		bus = bus->parent;
+	}
+
+	/* We have reached the root bus. */
+	if (bus->bridge)
+		acpi_dev_run_wake_enable(bus->bridge, enable);
+}
+
+static int acpi_pci_run_wake(struct pci_dev *dev, bool enable)
+{
+	if (dev->pme_interrupt)
+		return 0;
+
+	if (!acpi_dev_run_wake_enable(&dev->dev, enable))
+		return 0;
+
+	acpi_pci_propagate_run_wake(dev->bus, enable);
+	return 0;
+}
+
 static struct pci_platform_pm_ops acpi_pci_platform_pm = {
 	.is_manageable = acpi_pci_power_manageable,
 	.set_state = acpi_pci_set_power_state,
 	.choose_state = acpi_pci_choose_state,
 	.can_wakeup = acpi_pci_can_wakeup,
 	.sleep_wake = acpi_pci_sleep_wake,
+	.run_wake = acpi_pci_run_wake,
 };
 
 /* ACPI bus type */
Index: linux-2.6/drivers/pci/pci.c
===================================================================
--- linux-2.6.orig/drivers/pci/pci.c
+++ linux-2.6/drivers/pci/pci.c
@@ -21,6 +21,7 @@
 #include <linux/interrupt.h>
 #include <asm/dma.h>	/* isa_dma_bridge_buggy */
 #include <linux/device.h>
+#include <linux/pm_runtime.h>
 #include <asm/setup.h>
 #include "pci.h"
 
@@ -447,6 +448,12 @@ static inline int platform_pci_sleep_wak
 			pci_platform_pm->sleep_wake(dev, enable) : -ENODEV;
 }
 
+static inline int platform_pci_run_wake(struct pci_dev *dev, bool enable)
+{
+	return pci_platform_pm ?
+			pci_platform_pm->run_wake(dev, enable) : -ENODEV;
+}
+
 /**
  * pci_raw_set_power_state - Use PCI PM registers to set the power state of
  *                           given PCI device
@@ -1215,6 +1222,31 @@ bool pci_check_pme_status(struct pci_dev
 }
 
 /**
+ * pci_pme_wakeup - Wake up a PCI device if its PME Status bit is set.
+ * @dev: Device to handle.
+ * @ign: Ignored.
+ *
+ * Check if @dev has generated PME and queue a resume request for it in that
+ * case.
+ */
+static int pci_pme_wakeup(struct pci_dev *dev, void *ign)
+{
+	if (pci_check_pme_status(dev))
+		pm_request_resume(&dev->dev);
+	return 0;
+}
+
+/**
+ * pci_pme_wakeup_bus - Walk given bus and wake up devices on it, if necessary.
+ * @bus: Top bus of the subtree to walk.
+ */
+void pci_pme_wakeup_bus(struct pci_bus *bus)
+{
+	if (bus)
+		pci_walk_bus(bus, pci_pme_wakeup, NULL);
+}
+
+/**
  * pci_pme_capable - check the capability of PCI device to generate PME#
  * @dev: PCI device to handle.
  * @state: PCI state from which device will issue PME#.
@@ -1419,6 +1451,41 @@ int pci_back_from_sleep(struct pci_dev *
 }
 
 /**
+ * pci_dev_run_wake - Check if device can generate run-time wake-up events.
+ * @dev: Device to check.
+ *
+ * Return true if the device itself is cabable of generating wake-up events
+ * (through the platform or using the native PCIe PME) or if the device supports
+ * PME and one of its upstream bridges can generate wake-up events.
+ */
+bool pci_dev_run_wake(struct pci_dev *dev)
+{
+	struct pci_bus *bus = dev->bus;
+
+	if (device_run_wake(&dev->dev))
+		return true;
+
+	if (!dev->pme_support)
+		return false;
+
+	while (bus->parent) {
+		struct pci_dev *bridge = bus->self;
+
+		if (device_run_wake(&bridge->dev))
+			return true;
+
+		bus = bus->parent;
+	}
+
+	/* We have reached the root bus. */
+	if (bus->bridge)
+		return device_run_wake(bus->bridge);
+
+	return false;
+}
+EXPORT_SYMBOL_GPL(pci_dev_run_wake);
+
+/**
  * pci_pm_init - Initialize PM functions of given PCI device
  * @dev: PCI device to handle.
  */
Index: linux-2.6/include/linux/pci-acpi.h
===================================================================
--- linux-2.6.orig/include/linux/pci-acpi.h
+++ linux-2.6/include/linux/pci-acpi.h
@@ -11,6 +11,16 @@
 #include <linux/acpi.h>
 
 #ifdef CONFIG_ACPI
+extern acpi_status pci_acpi_add_hp_notifier(acpi_handle handle,
+					     acpi_notify_handler handler,
+					     void *context);
+extern acpi_status pci_acpi_remove_hp_notifier(acpi_handle handle,
+					       acpi_notify_handler handler);
+extern acpi_status pci_acpi_add_pm_notifier(struct acpi_device *dev,
+					     struct pci_dev *pci_dev,
+					     struct pci_bus *pci_bus);
+extern acpi_status pci_acpi_remove_pm_notifier(struct acpi_device *dev);
+
 static inline acpi_handle acpi_find_root_bridge_handle(struct pci_dev *pdev)
 {
 	struct pci_bus *pbus = pdev->bus;
Index: linux-2.6/drivers/acpi/pci_bind.c
===================================================================
--- linux-2.6.orig/drivers/acpi/pci_bind.c
+++ linux-2.6/drivers/acpi/pci_bind.c
@@ -26,7 +26,9 @@
 #include <linux/kernel.h>
 #include <linux/types.h>
 #include <linux/pci.h>
+#include <linux/pci-acpi.h>
 #include <linux/acpi.h>
+#include <linux/pm_runtime.h>
 #include <acpi/acpi_bus.h>
 #include <acpi/acpi_drivers.h>
 
@@ -38,7 +40,13 @@ static int acpi_pci_unbind(struct acpi_d
 	struct pci_dev *dev;
 
 	dev = acpi_get_pci_dev(device->handle);
-	if (!dev || !dev->subordinate)
+	if (!dev)
+		goto out;
+
+	device_set_run_wake(&dev->dev, false);
+	pci_acpi_remove_pm_notifier(device);
+
+	if (!dev->subordinate)
 		goto out;
 
 	acpi_pci_irq_del_prt(dev->subordinate);
@@ -62,6 +70,10 @@ static int acpi_pci_bind(struct acpi_dev
 	if (!dev)
 		return 0;
 
+	pci_acpi_add_pm_notifier(device, dev, dev->subordinate);
+	if (device->wakeup.flags.run_wake)
+		device_set_run_wake(&dev->dev, true);
+
 	/*
 	 * Install the 'bind' function to facilitate callbacks for
 	 * children of the P2P bridge.
Index: linux-2.6/drivers/pci/hotplug/acpiphp_glue.c
===================================================================
--- linux-2.6.orig/drivers/pci/hotplug/acpiphp_glue.c
+++ linux-2.6/drivers/pci/hotplug/acpiphp_glue.c
@@ -236,8 +236,7 @@ register_slot(acpi_handle handle, u32 lv
 
 	/* install notify handler */
 	if (!(newfunc->flags & FUNC_HAS_DCK)) {
-		status = acpi_install_notify_handler(handle,
-					     ACPI_SYSTEM_NOTIFY,
+		status = pci_acpi_add_hp_notifier(handle,
 					     handle_hotplug_event_func,
 					     newfunc);
 
@@ -288,14 +287,12 @@ static void init_bridge_misc(struct acpi
 	/* install notify handler */
 	if (bridge->type != BRIDGE_TYPE_HOST) {
 		if ((bridge->flags & BRIDGE_HAS_EJ0) && bridge->func) {
-			status = acpi_remove_notify_handler(bridge->func->handle,
-						ACPI_SYSTEM_NOTIFY,
+			status = pci_acpi_remove_hp_notifier(bridge->func->handle,
 						handle_hotplug_event_func);
 			if (ACPI_FAILURE(status))
 				err("failed to remove notify handler\n");
 		}
-		status = acpi_install_notify_handler(bridge->handle,
-					     ACPI_SYSTEM_NOTIFY,
+		status = pci_acpi_add_hp_notifier(bridge->handle,
 					     handle_hotplug_event_bridge,
 					     bridge);
 
@@ -505,15 +502,14 @@ static void cleanup_bridge(struct acpiph
 	acpi_status status;
 	acpi_handle handle = bridge->handle;
 
-	status = acpi_remove_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
+	status = pci_acpi_remove_hp_notifier(handle,
 					    handle_hotplug_event_bridge);
 	if (ACPI_FAILURE(status))
 		err("failed to remove notify handler\n");
 
 	if ((bridge->type != BRIDGE_TYPE_HOST) &&
 	    ((bridge->flags & BRIDGE_HAS_EJ0) && bridge->func)) {
-		status = acpi_install_notify_handler(bridge->func->handle,
-						ACPI_SYSTEM_NOTIFY,
+		status = pci_acpi_add_hp_notifier(bridge->func->handle,
 						handle_hotplug_event_func,
 						bridge->func);
 		if (ACPI_FAILURE(status))
@@ -529,8 +525,7 @@ static void cleanup_bridge(struct acpiph
 				unregister_dock_notifier(&func->nb);
 			}
 			if (!(func->flags & FUNC_HAS_DCK)) {
-				status = acpi_remove_notify_handler(func->handle,
-						ACPI_SYSTEM_NOTIFY,
+				status = pci_acpi_remove_hp_notifier(func->handle,
 						handle_hotplug_event_func);
 				if (ACPI_FAILURE(status))
 					err("failed to remove notify handler\n");
@@ -592,7 +587,7 @@ static void remove_bridge(acpi_handle ha
 	if (bridge)
 		cleanup_bridge(bridge);
 	else
-		acpi_remove_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
+		pci_acpi_remove_hp_notifier(handle,
 					   handle_hotplug_event_bridge);
 }
 
@@ -1278,8 +1273,8 @@ find_root_bridges(acpi_handle handle, u3
 	int *count = (int *)context;
 
 	if (acpi_is_root_bridge(handle)) {
-		acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
-				handle_hotplug_event_bridge, NULL);
+		pci_acpi_add_hp_notifier(handle,
+					handle_hotplug_event_bridge, NULL);
 			(*count)++;
 	}
 	return AE_OK ;
Index: linux-2.6/include/linux/pci.h
===================================================================
--- linux-2.6.orig/include/linux/pci.h
+++ linux-2.6/include/linux/pci.h
@@ -752,6 +752,7 @@ int pci_wake_from_d3(struct pci_dev *dev
 pci_power_t pci_target_state(struct pci_dev *dev);
 int pci_prepare_to_sleep(struct pci_dev *dev);
 int pci_back_from_sleep(struct pci_dev *dev);
+bool pci_dev_run_wake(struct pci_dev *dev);
 
 /* Functions for PCI Hotplug drivers to use */
 int pci_bus_find_capability(struct pci_bus *bus, unsigned int devfn, int cap);
Index: linux-2.6/include/acpi/acpi_bus.h
===================================================================
--- linux-2.6.orig/include/acpi/acpi_bus.h
+++ linux-2.6/include/acpi/acpi_bus.h
@@ -282,6 +282,7 @@ struct acpi_device {
 	struct device dev;
 	struct acpi_bus_ops bus_ops;	/* workaround for different code path for hotplug */
 	enum acpi_bus_removal_type removal_type;	/* indicate for different removal type */
+	void *bus_data;
 };
 
 static inline void *acpi_driver_data(struct acpi_device *d)
Index: linux-2.6/drivers/acpi/pci_root.c
===================================================================
--- linux-2.6.orig/drivers/acpi/pci_root.c
+++ linux-2.6/drivers/acpi/pci_root.c
@@ -30,6 +30,7 @@
 #include <linux/proc_fs.h>
 #include <linux/spinlock.h>
 #include <linux/pm.h>
+#include <linux/pm_runtime.h>
 #include <linux/pci.h>
 #include <linux/pci-acpi.h>
 #include <linux/acpi.h>
@@ -528,6 +529,10 @@ static int __devinit acpi_pci_root_add(s
 	if (flags != base_flags)
 		acpi_pci_osc_support(root, flags);
 
+	pci_acpi_add_pm_notifier(device, NULL, root->bus);
+	if (device->wakeup.flags.run_wake)
+		device_set_run_wake(root->bus->bridge, true);
+
 	return 0;
 
 end:
@@ -549,6 +554,9 @@ static int acpi_pci_root_remove(struct a
 {
 	struct acpi_pci_root *root = acpi_driver_data(device);
 
+	device_set_run_wake(root->bus->bridge, false);
+	pci_acpi_remove_pm_notifier(device);
+
 	kfree(root);
 	return 0;
 }


^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH 10/12] PCI / ACPI / PM: Platform support for PCI PME wake-up (rev. 6)
  2009-12-27 19:57 [PATCH 0/12] PCI run-time PM support (rev. 2) Rafael J. Wysocki
                   ` (15 preceding siblings ...)
  2009-12-27 20:07 ` [PATCH 10/12] PCI / ACPI / PM: Platform support for PCI PME wake-up (rev. 6) Rafael J. Wysocki
@ 2009-12-27 20:07 ` Rafael J. Wysocki
  2009-12-27 20:08 ` [PATCH 11/12] PCI PM: Run-time callbacks for PCI bus type (rev. 2) Rafael J. Wysocki
                   ` (5 subsequent siblings)
  22 siblings, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2009-12-27 20:07 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Linux PCI, LKML, ACPI Devel Maling List, Francois Romieu,
	pm list, Matthew Garrett

From: Rafael J. Wysocki <rjw@sisk.pl>

Although the majority of PCI devices can generate PMEs that in
principle may be used to wake up devices suspended at run time,
platform support is generally necessary to convert PMEs into wake-up
events that can be delivered to the kernel.  If ACPI is used for this
purpose, a PME generated by a PCI device will trigger the ACPI GPE
associated with the device to generate an ACPI wake-up event that we
can set up a handler for, provided that everything is configured
correctly.

Unfortunately, the subset of PCI devices that have GPEs associated
with them is quite limited and the other devices have to rely on
the GPEs associated with their upstream bridges and, possibly, the
root bridge to generate ACPI wake-up events in response to PMEs from
them.  Moreover, ACPI-based PCI hotplug also uses ACPI notify
handlers that in general may conflict with the PM notify handlers,
unless this issue is specifically taken care of.

Add ACPI platform support for PCI PME wake-up:
o Add a framework making is possible to use ACPI system notify
  handlers for both PM and hotplug at the same time and to take the
  wake-up GPE sharing into account.
o Add new PCI platform callback ->run_wake() to struct
  pci_platform_pm_ops allowing us to enable/disable the platform to
  generate wake-up events for given device.  Implemet this callback
  for the ACPI platform.
o Define ACPI wake-up handlers for PCI devices and PCI buses and make
  the PCI-ACPI binding code register wake-up notifiers for devices
  associated with wake-up GPEs.
o Add function pci_dev_run_wake() which can be used by PCI drivers to
  check if given device is capable of generating wake-up events at
  run time.

Developed in cooperation with Matthew Garrett <mjg@redhat.com>.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
 drivers/acpi/pci_bind.c            |   14 +
 drivers/acpi/pci_root.c            |    8 
 drivers/pci/hotplug/acpiphp_glue.c |   23 --
 drivers/pci/pci-acpi.c             |  341 +++++++++++++++++++++++++++++++++++++
 drivers/pci/pci.c                  |   67 +++++++
 drivers/pci/pci.h                  |    7 
 include/acpi/acpi_bus.h            |    1 
 include/linux/pci-acpi.h           |   10 +
 include/linux/pci.h                |    1 
 9 files changed, 457 insertions(+), 15 deletions(-)

Index: linux-2.6/drivers/pci/pci.h
===================================================================
--- linux-2.6.orig/drivers/pci/pci.h
+++ linux-2.6/drivers/pci/pci.h
@@ -35,6 +35,10 @@ int pci_probe_reset_function(struct pci_
  *
  * @sleep_wake: enables/disables the system wake up capability of given device
  *
+ * @run_wake: enables/disables the platform to generate run-time wake-up events
+ *		for given device (the device's wake-up capability has to be
+ *		enabled by @sleep_wake for this feature to work)
+ *
  * If given platform is generally capable of power managing PCI devices, all of
  * these callbacks are mandatory.
  */
@@ -44,12 +48,15 @@ struct pci_platform_pm_ops {
 	pci_power_t (*choose_state)(struct pci_dev *dev);
 	bool (*can_wakeup)(struct pci_dev *dev);
 	int (*sleep_wake)(struct pci_dev *dev, bool enable);
+	int (*run_wake)(struct pci_dev *dev, bool enable);
 };
 
 extern int pci_set_platform_pm(struct pci_platform_pm_ops *ops);
 extern void pci_update_current_state(struct pci_dev *dev, pci_power_t state);
 extern void pci_disable_enabled_device(struct pci_dev *dev);
 extern bool pci_check_pme_status(struct pci_dev *dev);
+extern int __pci_pme_wakeup(struct pci_dev *dev, void *ign);
+extern void pci_pme_wakeup_bus(struct pci_bus *bus);
 extern void pci_pm_init(struct pci_dev *dev);
 extern void platform_pci_wakeup_init(struct pci_dev *dev);
 extern void pci_allocate_cap_save_buffers(struct pci_dev *dev);
Index: linux-2.6/drivers/pci/pci-acpi.c
===================================================================
--- linux-2.6.orig/drivers/pci/pci-acpi.c
+++ linux-2.6/drivers/pci/pci-acpi.c
@@ -16,8 +16,276 @@
 #include <acpi/acpi_bus.h>
 
 #include <linux/pci-acpi.h>
+#include <linux/pm_runtime.h>
 #include "pci.h"
 
+static DEFINE_MUTEX(pci_acpi_notifier_mtx);
+
+struct pci_acpi_notify_data
+{
+	acpi_notify_handler hp_cb;
+	void *hp_data;
+	struct pci_bus *pci_bus;
+	struct pci_dev *pci_dev;
+};
+
+/**
+ * pci_acpi_event_fn - Universal system notification handler.
+ * @handle: ACPI handle of a device the notification is for.
+ * @event: Type of the signaled event.
+ * @ign: The value of this argument is ignored.
+ *
+ * Use @handle to obtain the address of the ACPI device object the event is
+ * signaled for.  If this is a wake-up event, execute the appropriate PME
+ * handler for the bus or device represented by it (or both, if @dev is a
+ * bridge).  If this is not a wake-up event, execute the hotplug notify handler
+ * for @handle.
+ */
+static void pci_acpi_event_fn(acpi_handle handle, u32 event, void *ign)
+{
+	struct acpi_device *dev;
+	struct pci_acpi_notify_data *nd;
+
+	if (ACPI_FAILURE(acpi_bus_get_device(handle, &dev))) {
+		pr_warning("ACPI handle has no context in %s!\n", __func__);
+		return;
+	}
+
+	mutex_lock(&pci_acpi_notifier_mtx);
+
+	nd = dev->bus_data;
+	if (!nd)
+		goto out;
+
+	if (event == ACPI_NOTIFY_DEVICE_WAKE) {
+		if (nd->pci_bus) {
+			pci_pme_wakeup_bus(nd->pci_bus);
+		}
+		if (nd->pci_dev) {
+			pci_check_pme_status(nd->pci_dev);
+			pm_request_resume(&nd->pci_dev->dev);
+		}
+	} else if (nd->hp_cb) {
+		nd->hp_cb(handle, event, nd->hp_data);
+	}
+
+ out:
+	mutex_unlock(&pci_acpi_notifier_mtx);
+}
+
+/**
+ * add_notify_data - Create a new notify data object for given ACPI device.
+ * @dev: Device to create the notify data object for.
+ */
+static struct pci_acpi_notify_data *add_notify_data(struct acpi_device *dev)
+{
+	struct pci_acpi_notify_data *nd;
+
+	nd = kzalloc(sizeof(*nd), GFP_KERNEL);
+	if (!nd)
+		return NULL;
+
+	dev->bus_data = nd;
+	return nd;
+}
+
+/**
+ * remove_notify_data - Remove the notify data object from given ACPI device.
+ * @dev: Device to remove the notify data object from.
+ */
+static void remove_notify_data(struct acpi_device *dev)
+{
+	kfree(dev->bus_data);
+	dev->bus_data = NULL;
+}
+
+/**
+ * pci_acpi_add_hp_notifier - Register a hotplug notifier for given device.
+ * @handle: ACPI handle of the device to register the notifier for.
+ * @handler: Callback to execute for hotplug events related to @handle.
+ * @context: Pointer to the context data to pass to @handler.
+ *
+ * Use @handle to get an ACPI device object and check if there is a notify data
+ * object for it.  If this is the case, add @handler and @context to the
+ * existing notify data object, unless there already is a hotplug handler in
+ * there.  Otherwise, create a new notify data object for the ACPI device
+ * associated with @handle and add @handler and @context to it.
+ */
+acpi_status pci_acpi_add_hp_notifier(acpi_handle handle,
+				     acpi_notify_handler handler, void *context)
+{
+	struct pci_acpi_notify_data *nd;
+	struct acpi_device *dev;
+	acpi_status status = AE_OK;
+
+	if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &dev)))
+		return AE_NOT_FOUND;
+
+	mutex_lock(&pci_acpi_notifier_mtx);
+
+	nd = dev->bus_data;
+	if (nd) {
+		if (!nd->hp_cb)
+			goto add;
+
+		status = AE_ALREADY_EXISTS;
+		goto out;
+	}
+
+	nd = add_notify_data(dev);
+	if (!nd) {
+		status = AE_NO_MEMORY;
+		goto out;
+	}
+
+	status = acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
+						pci_acpi_event_fn, NULL);
+	if (ACPI_FAILURE(status)) {
+		remove_notify_data(dev);
+		goto out;
+	}
+
+ add:
+	nd->hp_cb = handler;
+	nd->hp_data = context;
+
+ out:
+	mutex_unlock(&pci_acpi_notifier_mtx);
+
+	return status;
+}
+EXPORT_SYMBOL_GPL(pci_acpi_add_hp_notifier);
+
+/**
+ * pci_acpi_remove_hp_notifier - Unregister a hotplug notifier for given device.
+ * @handle: ACPI handle of the device to unregister the notifier for.
+ * @handler: Callback executed for hotplug events related to @handle.
+ *
+ * Remove the hotplug callback and the pointer to the hotplug context data from
+ * the notify data object belonging to the device represented by @handle.  If
+ * the notify data object is not necessary any more, remove it altogether.
+ */
+acpi_status pci_acpi_remove_hp_notifier(acpi_handle handle,
+					acpi_notify_handler handler)
+{
+	struct pci_acpi_notify_data *nd;
+	struct acpi_device *dev;
+	acpi_status status = AE_NOT_FOUND;
+
+	if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &dev)))
+		return AE_NOT_FOUND;
+
+	mutex_lock(&pci_acpi_notifier_mtx);
+
+	nd = dev->bus_data;
+	if (!nd)
+		goto out;
+
+	nd->hp_data = NULL;
+	nd->hp_cb = NULL;
+
+	if (nd->pci_bus || nd->pci_dev)
+		goto out;
+
+	status = acpi_remove_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
+						pci_acpi_event_fn);
+	remove_notify_data(dev);
+
+ out:
+	mutex_unlock(&pci_acpi_notifier_mtx);
+	return status;
+}
+EXPORT_SYMBOL_GPL(pci_acpi_remove_hp_notifier);
+
+/**
+ * pci_acpi_add_pm_notifier - Register PM notifier for given device.
+ * @dev: ACPI device to add the notifier for.
+ * @pci_dev: PCI device to check for the PME status if an event is signaled.
+ * @pci_bus: PCI bus to walk (checking PME status) if an event is signaled.
+ *
+ * Check if there is a notify data object for @dev and if that is the case, add
+ * @pci_dev to it as the device whose PME status should be checked whenever a PM
+ * event is signaled for @dev.  Also, add @pci_bus to it as the bus to walk
+ * checking the PME status of all devices on it whenever a PM event is signaled
+ * for @dev.
+ *
+ * Otherwise, create a new notify data object for @dev and add both @pci_dev and
+ * @pci_bus to it.
+ *
+ * NOTE: @dev need not be a run-wake or wake-up device to be a valid source of
+ * PM wake-up events.  For example, wake-up events may be generated for bridges
+ * if one of the devices below the bridge is signaling PME, even if the bridge
+ * itself doesn't have a wake-up GPE associated with it.
+ */
+acpi_status pci_acpi_add_pm_notifier(struct acpi_device *dev,
+				     struct pci_dev *pci_dev,
+				     struct pci_bus *pci_bus)
+{
+	struct pci_acpi_notify_data *nd;
+	acpi_status status = AE_OK;
+
+	mutex_lock(&pci_acpi_notifier_mtx);
+
+	nd = dev->bus_data;
+	if (nd)
+		goto add;
+
+	nd = add_notify_data(dev);
+	if (!nd) {
+		status = AE_NO_MEMORY;
+		goto out;
+	}
+
+	status = acpi_install_notify_handler(dev->handle, ACPI_SYSTEM_NOTIFY,
+						pci_acpi_event_fn, NULL);
+	if (ACPI_FAILURE(status)) {
+		remove_notify_data(dev);
+		goto out;
+	}
+
+ add:
+	nd->pci_dev = pci_dev;
+	nd->pci_bus = pci_bus;
+
+ out:
+	mutex_unlock(&pci_acpi_notifier_mtx);
+	return status;
+}
+
+/**
+ * pci_acpi_remove_pm_notifier - Unregister PM notifier for given device.
+ * @dev: ACPI device to remove the notifier from.
+ *
+ * Find the notify data object for @dev and clear its @pci_dev and @pci_bus
+ * fields.  If the notify data object is not necessary any more after that,
+ * remove it too.
+ */
+acpi_status pci_acpi_remove_pm_notifier(struct acpi_device *dev)
+{
+	struct pci_acpi_notify_data *nd;
+	acpi_status status = AE_NOT_FOUND;
+
+	mutex_lock(&pci_acpi_notifier_mtx);
+
+	nd = dev->bus_data;
+	if (!nd)
+		goto out;
+
+	nd->pci_dev = NULL;
+	nd->pci_bus = NULL;
+
+	if (nd->hp_cb)
+		goto out;
+
+	status = acpi_remove_notify_handler(dev->handle, ACPI_SYSTEM_NOTIFY,
+						pci_acpi_event_fn);
+	remove_notify_data(dev);
+
+ out:
+	mutex_unlock(&pci_acpi_notifier_mtx);
+	return status;
+}
+
 /*
  * _SxD returns the D-state with the highest power
  * (lowest D-state number) supported in the S-state "x".
@@ -131,12 +399,85 @@ static int acpi_pci_sleep_wake(struct pc
 	return 0;
 }
 
+/**
+ * acpi_dev_run_wake_enable - Enable/disable wake-up for given device.
+ * @phys_dev: Device to enable/disable the platform to wake-up the system for.
+ * @enable: Whether enable or disable the wake-up functionality.
+ *
+ * Find the ACPI device object corresponding to @pci_dev and try to
+ * enable/disable the GPE associated with it.
+ */
+static int acpi_dev_run_wake_enable(struct device *phys_dev, bool enable)
+{
+	struct acpi_device *dev;
+	acpi_handle handle;
+	int error = -ENODEV;
+
+	if (!device_run_wake(phys_dev))
+		return -EINVAL;
+
+	handle = DEVICE_ACPI_HANDLE(phys_dev);
+	if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &dev))) {
+		dev_dbg(phys_dev, "ACPI handle has no context in %s!\n",
+			__func__);
+		return -ENODEV;
+	}
+
+	if (enable) {
+		if (!dev->wakeup.run_wake_count++) {
+			acpi_pm_wakeup_power(dev, true);
+			acpi_ref_runtime_gpe(dev->wakeup.gpe_device,
+						dev->wakeup.gpe_number);
+		}
+	} else if (dev->wakeup.run_wake_count > 0) {
+		if (!--dev->wakeup.run_wake_count) {
+			acpi_unref_runtime_gpe(dev->wakeup.gpe_device,
+						dev->wakeup.gpe_number);
+			acpi_pm_wakeup_power(dev, false);
+		}
+	} else {
+		error = -EALREADY;
+	}
+
+	return error;
+}
+
+static void acpi_pci_propagate_run_wake(struct pci_bus *bus, bool enable)
+{
+	while (bus->parent) {
+		struct pci_dev *bridge = bus->self;
+
+		if (bridge->pme_interrupt)
+			return;
+		if (!acpi_dev_run_wake_enable(&bridge->dev, enable))
+			return;
+		bus = bus->parent;
+	}
+
+	/* We have reached the root bus. */
+	if (bus->bridge)
+		acpi_dev_run_wake_enable(bus->bridge, enable);
+}
+
+static int acpi_pci_run_wake(struct pci_dev *dev, bool enable)
+{
+	if (dev->pme_interrupt)
+		return 0;
+
+	if (!acpi_dev_run_wake_enable(&dev->dev, enable))
+		return 0;
+
+	acpi_pci_propagate_run_wake(dev->bus, enable);
+	return 0;
+}
+
 static struct pci_platform_pm_ops acpi_pci_platform_pm = {
 	.is_manageable = acpi_pci_power_manageable,
 	.set_state = acpi_pci_set_power_state,
 	.choose_state = acpi_pci_choose_state,
 	.can_wakeup = acpi_pci_can_wakeup,
 	.sleep_wake = acpi_pci_sleep_wake,
+	.run_wake = acpi_pci_run_wake,
 };
 
 /* ACPI bus type */
Index: linux-2.6/drivers/pci/pci.c
===================================================================
--- linux-2.6.orig/drivers/pci/pci.c
+++ linux-2.6/drivers/pci/pci.c
@@ -21,6 +21,7 @@
 #include <linux/interrupt.h>
 #include <asm/dma.h>	/* isa_dma_bridge_buggy */
 #include <linux/device.h>
+#include <linux/pm_runtime.h>
 #include <asm/setup.h>
 #include "pci.h"
 
@@ -447,6 +448,12 @@ static inline int platform_pci_sleep_wak
 			pci_platform_pm->sleep_wake(dev, enable) : -ENODEV;
 }
 
+static inline int platform_pci_run_wake(struct pci_dev *dev, bool enable)
+{
+	return pci_platform_pm ?
+			pci_platform_pm->run_wake(dev, enable) : -ENODEV;
+}
+
 /**
  * pci_raw_set_power_state - Use PCI PM registers to set the power state of
  *                           given PCI device
@@ -1215,6 +1222,31 @@ bool pci_check_pme_status(struct pci_dev
 }
 
 /**
+ * pci_pme_wakeup - Wake up a PCI device if its PME Status bit is set.
+ * @dev: Device to handle.
+ * @ign: Ignored.
+ *
+ * Check if @dev has generated PME and queue a resume request for it in that
+ * case.
+ */
+static int pci_pme_wakeup(struct pci_dev *dev, void *ign)
+{
+	if (pci_check_pme_status(dev))
+		pm_request_resume(&dev->dev);
+	return 0;
+}
+
+/**
+ * pci_pme_wakeup_bus - Walk given bus and wake up devices on it, if necessary.
+ * @bus: Top bus of the subtree to walk.
+ */
+void pci_pme_wakeup_bus(struct pci_bus *bus)
+{
+	if (bus)
+		pci_walk_bus(bus, pci_pme_wakeup, NULL);
+}
+
+/**
  * pci_pme_capable - check the capability of PCI device to generate PME#
  * @dev: PCI device to handle.
  * @state: PCI state from which device will issue PME#.
@@ -1419,6 +1451,41 @@ int pci_back_from_sleep(struct pci_dev *
 }
 
 /**
+ * pci_dev_run_wake - Check if device can generate run-time wake-up events.
+ * @dev: Device to check.
+ *
+ * Return true if the device itself is cabable of generating wake-up events
+ * (through the platform or using the native PCIe PME) or if the device supports
+ * PME and one of its upstream bridges can generate wake-up events.
+ */
+bool pci_dev_run_wake(struct pci_dev *dev)
+{
+	struct pci_bus *bus = dev->bus;
+
+	if (device_run_wake(&dev->dev))
+		return true;
+
+	if (!dev->pme_support)
+		return false;
+
+	while (bus->parent) {
+		struct pci_dev *bridge = bus->self;
+
+		if (device_run_wake(&bridge->dev))
+			return true;
+
+		bus = bus->parent;
+	}
+
+	/* We have reached the root bus. */
+	if (bus->bridge)
+		return device_run_wake(bus->bridge);
+
+	return false;
+}
+EXPORT_SYMBOL_GPL(pci_dev_run_wake);
+
+/**
  * pci_pm_init - Initialize PM functions of given PCI device
  * @dev: PCI device to handle.
  */
Index: linux-2.6/include/linux/pci-acpi.h
===================================================================
--- linux-2.6.orig/include/linux/pci-acpi.h
+++ linux-2.6/include/linux/pci-acpi.h
@@ -11,6 +11,16 @@
 #include <linux/acpi.h>
 
 #ifdef CONFIG_ACPI
+extern acpi_status pci_acpi_add_hp_notifier(acpi_handle handle,
+					     acpi_notify_handler handler,
+					     void *context);
+extern acpi_status pci_acpi_remove_hp_notifier(acpi_handle handle,
+					       acpi_notify_handler handler);
+extern acpi_status pci_acpi_add_pm_notifier(struct acpi_device *dev,
+					     struct pci_dev *pci_dev,
+					     struct pci_bus *pci_bus);
+extern acpi_status pci_acpi_remove_pm_notifier(struct acpi_device *dev);
+
 static inline acpi_handle acpi_find_root_bridge_handle(struct pci_dev *pdev)
 {
 	struct pci_bus *pbus = pdev->bus;
Index: linux-2.6/drivers/acpi/pci_bind.c
===================================================================
--- linux-2.6.orig/drivers/acpi/pci_bind.c
+++ linux-2.6/drivers/acpi/pci_bind.c
@@ -26,7 +26,9 @@
 #include <linux/kernel.h>
 #include <linux/types.h>
 #include <linux/pci.h>
+#include <linux/pci-acpi.h>
 #include <linux/acpi.h>
+#include <linux/pm_runtime.h>
 #include <acpi/acpi_bus.h>
 #include <acpi/acpi_drivers.h>
 
@@ -38,7 +40,13 @@ static int acpi_pci_unbind(struct acpi_d
 	struct pci_dev *dev;
 
 	dev = acpi_get_pci_dev(device->handle);
-	if (!dev || !dev->subordinate)
+	if (!dev)
+		goto out;
+
+	device_set_run_wake(&dev->dev, false);
+	pci_acpi_remove_pm_notifier(device);
+
+	if (!dev->subordinate)
 		goto out;
 
 	acpi_pci_irq_del_prt(dev->subordinate);
@@ -62,6 +70,10 @@ static int acpi_pci_bind(struct acpi_dev
 	if (!dev)
 		return 0;
 
+	pci_acpi_add_pm_notifier(device, dev, dev->subordinate);
+	if (device->wakeup.flags.run_wake)
+		device_set_run_wake(&dev->dev, true);
+
 	/*
 	 * Install the 'bind' function to facilitate callbacks for
 	 * children of the P2P bridge.
Index: linux-2.6/drivers/pci/hotplug/acpiphp_glue.c
===================================================================
--- linux-2.6.orig/drivers/pci/hotplug/acpiphp_glue.c
+++ linux-2.6/drivers/pci/hotplug/acpiphp_glue.c
@@ -236,8 +236,7 @@ register_slot(acpi_handle handle, u32 lv
 
 	/* install notify handler */
 	if (!(newfunc->flags & FUNC_HAS_DCK)) {
-		status = acpi_install_notify_handler(handle,
-					     ACPI_SYSTEM_NOTIFY,
+		status = pci_acpi_add_hp_notifier(handle,
 					     handle_hotplug_event_func,
 					     newfunc);
 
@@ -288,14 +287,12 @@ static void init_bridge_misc(struct acpi
 	/* install notify handler */
 	if (bridge->type != BRIDGE_TYPE_HOST) {
 		if ((bridge->flags & BRIDGE_HAS_EJ0) && bridge->func) {
-			status = acpi_remove_notify_handler(bridge->func->handle,
-						ACPI_SYSTEM_NOTIFY,
+			status = pci_acpi_remove_hp_notifier(bridge->func->handle,
 						handle_hotplug_event_func);
 			if (ACPI_FAILURE(status))
 				err("failed to remove notify handler\n");
 		}
-		status = acpi_install_notify_handler(bridge->handle,
-					     ACPI_SYSTEM_NOTIFY,
+		status = pci_acpi_add_hp_notifier(bridge->handle,
 					     handle_hotplug_event_bridge,
 					     bridge);
 
@@ -505,15 +502,14 @@ static void cleanup_bridge(struct acpiph
 	acpi_status status;
 	acpi_handle handle = bridge->handle;
 
-	status = acpi_remove_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
+	status = pci_acpi_remove_hp_notifier(handle,
 					    handle_hotplug_event_bridge);
 	if (ACPI_FAILURE(status))
 		err("failed to remove notify handler\n");
 
 	if ((bridge->type != BRIDGE_TYPE_HOST) &&
 	    ((bridge->flags & BRIDGE_HAS_EJ0) && bridge->func)) {
-		status = acpi_install_notify_handler(bridge->func->handle,
-						ACPI_SYSTEM_NOTIFY,
+		status = pci_acpi_add_hp_notifier(bridge->func->handle,
 						handle_hotplug_event_func,
 						bridge->func);
 		if (ACPI_FAILURE(status))
@@ -529,8 +525,7 @@ static void cleanup_bridge(struct acpiph
 				unregister_dock_notifier(&func->nb);
 			}
 			if (!(func->flags & FUNC_HAS_DCK)) {
-				status = acpi_remove_notify_handler(func->handle,
-						ACPI_SYSTEM_NOTIFY,
+				status = pci_acpi_remove_hp_notifier(func->handle,
 						handle_hotplug_event_func);
 				if (ACPI_FAILURE(status))
 					err("failed to remove notify handler\n");
@@ -592,7 +587,7 @@ static void remove_bridge(acpi_handle ha
 	if (bridge)
 		cleanup_bridge(bridge);
 	else
-		acpi_remove_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
+		pci_acpi_remove_hp_notifier(handle,
 					   handle_hotplug_event_bridge);
 }
 
@@ -1278,8 +1273,8 @@ find_root_bridges(acpi_handle handle, u3
 	int *count = (int *)context;
 
 	if (acpi_is_root_bridge(handle)) {
-		acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
-				handle_hotplug_event_bridge, NULL);
+		pci_acpi_add_hp_notifier(handle,
+					handle_hotplug_event_bridge, NULL);
 			(*count)++;
 	}
 	return AE_OK ;
Index: linux-2.6/include/linux/pci.h
===================================================================
--- linux-2.6.orig/include/linux/pci.h
+++ linux-2.6/include/linux/pci.h
@@ -752,6 +752,7 @@ int pci_wake_from_d3(struct pci_dev *dev
 pci_power_t pci_target_state(struct pci_dev *dev);
 int pci_prepare_to_sleep(struct pci_dev *dev);
 int pci_back_from_sleep(struct pci_dev *dev);
+bool pci_dev_run_wake(struct pci_dev *dev);
 
 /* Functions for PCI Hotplug drivers to use */
 int pci_bus_find_capability(struct pci_bus *bus, unsigned int devfn, int cap);
Index: linux-2.6/include/acpi/acpi_bus.h
===================================================================
--- linux-2.6.orig/include/acpi/acpi_bus.h
+++ linux-2.6/include/acpi/acpi_bus.h
@@ -282,6 +282,7 @@ struct acpi_device {
 	struct device dev;
 	struct acpi_bus_ops bus_ops;	/* workaround for different code path for hotplug */
 	enum acpi_bus_removal_type removal_type;	/* indicate for different removal type */
+	void *bus_data;
 };
 
 static inline void *acpi_driver_data(struct acpi_device *d)
Index: linux-2.6/drivers/acpi/pci_root.c
===================================================================
--- linux-2.6.orig/drivers/acpi/pci_root.c
+++ linux-2.6/drivers/acpi/pci_root.c
@@ -30,6 +30,7 @@
 #include <linux/proc_fs.h>
 #include <linux/spinlock.h>
 #include <linux/pm.h>
+#include <linux/pm_runtime.h>
 #include <linux/pci.h>
 #include <linux/pci-acpi.h>
 #include <linux/acpi.h>
@@ -528,6 +529,10 @@ static int __devinit acpi_pci_root_add(s
 	if (flags != base_flags)
 		acpi_pci_osc_support(root, flags);
 
+	pci_acpi_add_pm_notifier(device, NULL, root->bus);
+	if (device->wakeup.flags.run_wake)
+		device_set_run_wake(root->bus->bridge, true);
+
 	return 0;
 
 end:
@@ -549,6 +554,9 @@ static int acpi_pci_root_remove(struct a
 {
 	struct acpi_pci_root *root = acpi_driver_data(device);
 
+	device_set_run_wake(root->bus->bridge, false);
+	pci_acpi_remove_pm_notifier(device);
+
 	kfree(root);
 	return 0;
 }

^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH 11/12] PCI PM: Run-time callbacks for PCI bus type (rev. 2)
  2009-12-27 19:57 [PATCH 0/12] PCI run-time PM support (rev. 2) Rafael J. Wysocki
                   ` (17 preceding siblings ...)
  2009-12-27 20:08 ` [PATCH 11/12] PCI PM: Run-time callbacks for PCI bus type (rev. 2) Rafael J. Wysocki
@ 2009-12-27 20:08 ` Rafael J. Wysocki
  2010-01-06 22:06   ` Jesse Barnes
  2010-01-06 22:06   ` Jesse Barnes
  2009-12-27 20:11 ` [PATCH 12/12] PM / r8169: Add simplified run-time PM support Rafael J. Wysocki
                   ` (3 subsequent siblings)
  22 siblings, 2 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2009-12-27 20:08 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Matthew Garrett, Len Brown, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu

From: Rafael J. Wysocki <rjw@sisk.pl>

Introduce run-time PM callbacks for the PCI bus type.  Make the new
callbacks work in analogy with the existing system sleep PM
callbacks, so that the drivers already converted to struct dev_pm_ops
can use their suspend and resume routines for run-time PM without
modifications.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
 drivers/pci/pci-driver.c |  105 ++++++++++++++++++++++++++++++++++++++++++++---
 drivers/pci/pci.c        |   43 ++++++++++++++++---
 drivers/pci/pci.h        |    1 
 include/linux/pci.h      |    9 +++-
 kernel/power/Kconfig     |    5 ++
 5 files changed, 151 insertions(+), 12 deletions(-)

Index: linux-2.6/kernel/power/Kconfig
===================================================================
--- linux-2.6.orig/kernel/power/Kconfig
+++ linux-2.6/kernel/power/Kconfig
@@ -241,3 +241,8 @@ config PM_WAKEUP
 	bool
 	depends on SUSPEND || HIBERNATION || PM_RUNTIME
 	default y
+
+config PM_OPS
+	bool
+	depends on PM_SLEEP || PM_RUNTIME
+	default y
Index: linux-2.6/drivers/pci/pci-driver.c
===================================================================
--- linux-2.6.orig/drivers/pci/pci-driver.c
+++ linux-2.6/drivers/pci/pci-driver.c
@@ -17,6 +17,7 @@
 #include <linux/slab.h>
 #include <linux/sched.h>
 #include <linux/cpu.h>
+#include <linux/pm_runtime.h>
 #include "pci.h"
 
 struct pci_dynid {
@@ -537,7 +538,7 @@ static int pci_restore_standard_config(s
 	return pci_restore_state(pci_dev);
 }
 
-static void pci_pm_default_resume_noirq(struct pci_dev *pci_dev)
+static void pci_pm_default_resume_early(struct pci_dev *pci_dev)
 {
 	pci_restore_standard_config(pci_dev);
 	pci_fixup_device(pci_fixup_resume_early, pci_dev);
@@ -581,6 +582,17 @@ static int pci_pm_prepare(struct device 
 	struct device_driver *drv = dev->driver;
 	int error = 0;
 
+	/*
+	 * PCI devices suspended at run time need to be resumed at this
+	 * point, because in general it is necessary to reconfigure them for
+	 * system suspend.  Namely, if the device is supposed to wake up the
+	 * system from the sleep state, we may need to reconfigure it for this
+	 * purpose.  In turn, if the device is not supposed to wake up the
+	 * system from the sleep state, we'll have to prevent it from signaling
+	 * wake-up.
+	 */
+	pm_runtime_resume(dev);
+
 	if (drv && drv->pm && drv->pm->prepare)
 		error = drv->pm->prepare(dev);
 
@@ -681,7 +693,7 @@ static int pci_pm_resume_noirq(struct de
 	struct device_driver *drv = dev->driver;
 	int error = 0;
 
-	pci_pm_default_resume_noirq(pci_dev);
+	pci_pm_default_resume_early(pci_dev);
 
 	if (pci_has_legacy_pm_support(pci_dev))
 		return pci_legacy_resume_early(dev);
@@ -879,7 +891,7 @@ static int pci_pm_restore_noirq(struct d
 	struct device_driver *drv = dev->driver;
 	int error = 0;
 
-	pci_pm_default_resume_noirq(pci_dev);
+	pci_pm_default_resume_early(pci_dev);
 
 	if (pci_has_legacy_pm_support(pci_dev))
 		return pci_legacy_resume_early(dev);
@@ -931,6 +943,86 @@ static int pci_pm_restore(struct device 
 
 #endif /* !CONFIG_HIBERNATION */
 
+#endif /* !CONFIG_PM_SLEEP */
+
+#ifdef CONFIG_PM_RUNTIME
+
+static int pci_pm_runtime_suspend(struct device *dev)
+{
+	struct pci_dev *pci_dev = to_pci_dev(dev);
+	const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+	pci_power_t prev = pci_dev->current_state;
+	int error;
+
+	if (!pm || !pm->runtime_suspend)
+		return -ENOSYS;
+
+	error = pm->runtime_suspend(dev);
+	suspend_report_result(pm->runtime_suspend, error);
+	if (error)
+		return error;
+
+	pci_fixup_device(pci_fixup_suspend, pci_dev);
+
+	if (!pci_dev->state_saved && pci_dev->current_state != PCI_D0
+	    && pci_dev->current_state != PCI_UNKNOWN) {
+		WARN_ONCE(pci_dev->current_state != prev,
+			"PCI PM: State of device not saved by %pF\n",
+			pm->runtime_suspend);
+		return 0;
+	}
+
+	if (!pci_dev->state_saved)
+		pci_save_state(pci_dev);
+
+	pci_finish_runtime_suspend(pci_dev);
+
+	return 0;
+}
+
+static int pci_pm_runtime_resume(struct device *dev)
+{
+	struct pci_dev *pci_dev = to_pci_dev(dev);
+	const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+
+	if (!pm || !pm->runtime_resume)
+		return -ENOSYS;
+
+	pci_pm_default_resume_early(pci_dev);
+	__pci_enable_wake(pci_dev, PCI_D0, true, false);
+	pci_fixup_device(pci_fixup_resume, pci_dev);
+
+	return pm->runtime_resume(dev);
+}
+
+static int pci_pm_runtime_idle(struct device *dev)
+{
+	const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+
+	if (!pm)
+		return -ENOSYS;
+
+	if (pm->runtime_idle) {
+		int ret = pm->runtime_idle(dev);
+		if (ret)
+			return ret;
+	}
+
+	pm_runtime_suspend(dev);
+
+	return 0;
+}
+
+#else /* !CONFIG_PM_RUNTIME */
+
+#define pci_pm_runtime_suspend	NULL
+#define pci_pm_runtime_resume	NULL
+#define pci_pm_runtime_idle	NULL
+
+#endif /* !CONFIG_PM_RUNTIME */
+
+#ifdef CONFIG_PM_OPS
+
 const struct dev_pm_ops pci_dev_pm_ops = {
 	.prepare = pci_pm_prepare,
 	.complete = pci_pm_complete,
@@ -946,15 +1038,18 @@ const 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,
+	.runtime_suspend = pci_pm_runtime_suspend,
+	.runtime_resume = pci_pm_runtime_resume,
+	.runtime_idle = pci_pm_runtime_idle,
 };
 
 #define PCI_PM_OPS_PTR	(&pci_dev_pm_ops)
 
-#else /* !CONFIG_PM_SLEEP */
+#else /* !COMFIG_PM_OPS */
 
 #define PCI_PM_OPS_PTR	NULL
 
-#endif /* !CONFIG_PM_SLEEP */
+#endif /* !COMFIG_PM_OPS */
 
 /**
  * __pci_register_driver - register a new pci driver
Index: linux-2.6/drivers/pci/pci.c
===================================================================
--- linux-2.6.orig/drivers/pci/pci.c
+++ linux-2.6/drivers/pci/pci.c
@@ -1287,9 +1287,10 @@ void pci_pme_active(struct pci_dev *dev,
 }
 
 /**
- * pci_enable_wake - enable PCI device as wakeup event source
+ * __pci_enable_wake - enable PCI device as wakeup event source
  * @dev: PCI device affected
  * @state: PCI state from which device will issue wakeup events
+ * @runtime: True if the events are to be generated at run time
  * @enable: True to enable event generation; false to disable
  *
  * This enables the device as a wakeup event source, or disables it.
@@ -1305,11 +1306,12 @@ void pci_pme_active(struct pci_dev *dev,
  * Error code depending on the platform is returned if both the platform and
  * the native mechanism fail to enable the generation of wake-up events
  */
-int pci_enable_wake(struct pci_dev *dev, pci_power_t state, bool enable)
+int __pci_enable_wake(struct pci_dev *dev, pci_power_t state,
+		      bool runtime, bool enable)
 {
 	int ret = 0;
 
-	if (enable && !device_may_wakeup(&dev->dev))
+	if (enable && !runtime && !device_may_wakeup(&dev->dev))
 		return -EINVAL;
 
 	/* Don't do the same thing twice in a row for one device. */
@@ -1329,19 +1331,24 @@ int pci_enable_wake(struct pci_dev *dev,
 			pci_pme_active(dev, true);
 		else
 			ret = 1;
-		error = platform_pci_sleep_wake(dev, true);
+		error = runtime ? platform_pci_run_wake(dev, true) :
+					platform_pci_sleep_wake(dev, true);
 		if (ret)
 			ret = error;
 		if (!ret)
 			dev->wakeup_prepared = true;
 	} else {
-		platform_pci_sleep_wake(dev, false);
+		if (runtime)
+			platform_pci_run_wake(dev, false);
+		else
+			platform_pci_sleep_wake(dev, false);
 		pci_pme_active(dev, false);
 		dev->wakeup_prepared = false;
 	}
 
 	return ret;
 }
+EXPORT_SYMBOL(__pci_enable_wake);
 
 /**
  * pci_wake_from_d3 - enable/disable device to wake up from D3_hot or D3_cold
@@ -1451,6 +1458,31 @@ int pci_back_from_sleep(struct pci_dev *
 }
 
 /**
+ * pci_finish_runtime_suspend - Carry out PCI-specific part of runtime suspend.
+ * @dev: PCI device being suspended.
+ *
+ * Prepare @dev to generate wake-up events at run time and put it into a low
+ * power state.
+ */
+int pci_finish_runtime_suspend(struct pci_dev *dev)
+{
+	pci_power_t target_state = pci_target_state(dev);
+	int error;
+
+	if (target_state == PCI_POWER_ERROR)
+		return -EIO;
+
+	__pci_enable_wake(dev, target_state, true, pci_dev_run_wake(dev));
+
+	error = pci_set_power_state(dev, target_state);
+
+	if (error)
+		__pci_enable_wake(dev, target_state, true, false);
+
+	return error;
+}
+
+/**
  * pci_dev_run_wake - Check if device can generate run-time wake-up events.
  * @dev: Device to check.
  *
@@ -2954,7 +2986,6 @@ EXPORT_SYMBOL(pci_save_state);
 EXPORT_SYMBOL(pci_restore_state);
 EXPORT_SYMBOL(pci_pme_capable);
 EXPORT_SYMBOL(pci_pme_active);
-EXPORT_SYMBOL(pci_enable_wake);
 EXPORT_SYMBOL(pci_wake_from_d3);
 EXPORT_SYMBOL(pci_target_state);
 EXPORT_SYMBOL(pci_prepare_to_sleep);
Index: linux-2.6/include/linux/pci.h
===================================================================
--- linux-2.6.orig/include/linux/pci.h
+++ linux-2.6/include/linux/pci.h
@@ -747,13 +747,20 @@ int pci_set_power_state(struct pci_dev *
 pci_power_t pci_choose_state(struct pci_dev *dev, pm_message_t state);
 bool pci_pme_capable(struct pci_dev *dev, pci_power_t state);
 void pci_pme_active(struct pci_dev *dev, bool enable);
-int pci_enable_wake(struct pci_dev *dev, pci_power_t state, bool enable);
+int __pci_enable_wake(struct pci_dev *dev, pci_power_t state,
+		      bool runtime, bool enable);
 int pci_wake_from_d3(struct pci_dev *dev, bool enable);
 pci_power_t pci_target_state(struct pci_dev *dev);
 int pci_prepare_to_sleep(struct pci_dev *dev);
 int pci_back_from_sleep(struct pci_dev *dev);
 bool pci_dev_run_wake(struct pci_dev *dev);
 
+static inline int pci_enable_wake(struct pci_dev *dev, pci_power_t state,
+				  bool enable)
+{
+	return __pci_enable_wake(dev, state, false, enable);
+}
+
 /* Functions for PCI Hotplug drivers to use */
 int pci_bus_find_capability(struct pci_bus *bus, unsigned int devfn, int cap);
 #ifdef CONFIG_HOTPLUG
Index: linux-2.6/drivers/pci/pci.h
===================================================================
--- linux-2.6.orig/drivers/pci/pci.h
+++ linux-2.6/drivers/pci/pci.h
@@ -55,6 +55,7 @@ extern int pci_set_platform_pm(struct pc
 extern void pci_update_current_state(struct pci_dev *dev, pci_power_t state);
 extern void pci_disable_enabled_device(struct pci_dev *dev);
 extern bool pci_check_pme_status(struct pci_dev *dev);
+extern int pci_finish_runtime_suspend(struct pci_dev *dev);
 extern int __pci_pme_wakeup(struct pci_dev *dev, void *ign);
 extern void pci_pme_wakeup_bus(struct pci_bus *bus);
 extern void pci_pm_init(struct pci_dev *dev);

^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH 11/12] PCI PM: Run-time callbacks for PCI bus type (rev. 2)
  2009-12-27 19:57 [PATCH 0/12] PCI run-time PM support (rev. 2) Rafael J. Wysocki
                   ` (16 preceding siblings ...)
  2009-12-27 20:07 ` Rafael J. Wysocki
@ 2009-12-27 20:08 ` Rafael J. Wysocki
  2009-12-27 20:08 ` Rafael J. Wysocki
                   ` (4 subsequent siblings)
  22 siblings, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2009-12-27 20:08 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Linux PCI, LKML, ACPI Devel Maling List, Francois Romieu,
	pm list, Matthew Garrett

From: Rafael J. Wysocki <rjw@sisk.pl>

Introduce run-time PM callbacks for the PCI bus type.  Make the new
callbacks work in analogy with the existing system sleep PM
callbacks, so that the drivers already converted to struct dev_pm_ops
can use their suspend and resume routines for run-time PM without
modifications.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
 drivers/pci/pci-driver.c |  105 ++++++++++++++++++++++++++++++++++++++++++++---
 drivers/pci/pci.c        |   43 ++++++++++++++++---
 drivers/pci/pci.h        |    1 
 include/linux/pci.h      |    9 +++-
 kernel/power/Kconfig     |    5 ++
 5 files changed, 151 insertions(+), 12 deletions(-)

Index: linux-2.6/kernel/power/Kconfig
===================================================================
--- linux-2.6.orig/kernel/power/Kconfig
+++ linux-2.6/kernel/power/Kconfig
@@ -241,3 +241,8 @@ config PM_WAKEUP
 	bool
 	depends on SUSPEND || HIBERNATION || PM_RUNTIME
 	default y
+
+config PM_OPS
+	bool
+	depends on PM_SLEEP || PM_RUNTIME
+	default y
Index: linux-2.6/drivers/pci/pci-driver.c
===================================================================
--- linux-2.6.orig/drivers/pci/pci-driver.c
+++ linux-2.6/drivers/pci/pci-driver.c
@@ -17,6 +17,7 @@
 #include <linux/slab.h>
 #include <linux/sched.h>
 #include <linux/cpu.h>
+#include <linux/pm_runtime.h>
 #include "pci.h"
 
 struct pci_dynid {
@@ -537,7 +538,7 @@ static int pci_restore_standard_config(s
 	return pci_restore_state(pci_dev);
 }
 
-static void pci_pm_default_resume_noirq(struct pci_dev *pci_dev)
+static void pci_pm_default_resume_early(struct pci_dev *pci_dev)
 {
 	pci_restore_standard_config(pci_dev);
 	pci_fixup_device(pci_fixup_resume_early, pci_dev);
@@ -581,6 +582,17 @@ static int pci_pm_prepare(struct device 
 	struct device_driver *drv = dev->driver;
 	int error = 0;
 
+	/*
+	 * PCI devices suspended at run time need to be resumed at this
+	 * point, because in general it is necessary to reconfigure them for
+	 * system suspend.  Namely, if the device is supposed to wake up the
+	 * system from the sleep state, we may need to reconfigure it for this
+	 * purpose.  In turn, if the device is not supposed to wake up the
+	 * system from the sleep state, we'll have to prevent it from signaling
+	 * wake-up.
+	 */
+	pm_runtime_resume(dev);
+
 	if (drv && drv->pm && drv->pm->prepare)
 		error = drv->pm->prepare(dev);
 
@@ -681,7 +693,7 @@ static int pci_pm_resume_noirq(struct de
 	struct device_driver *drv = dev->driver;
 	int error = 0;
 
-	pci_pm_default_resume_noirq(pci_dev);
+	pci_pm_default_resume_early(pci_dev);
 
 	if (pci_has_legacy_pm_support(pci_dev))
 		return pci_legacy_resume_early(dev);
@@ -879,7 +891,7 @@ static int pci_pm_restore_noirq(struct d
 	struct device_driver *drv = dev->driver;
 	int error = 0;
 
-	pci_pm_default_resume_noirq(pci_dev);
+	pci_pm_default_resume_early(pci_dev);
 
 	if (pci_has_legacy_pm_support(pci_dev))
 		return pci_legacy_resume_early(dev);
@@ -931,6 +943,86 @@ static int pci_pm_restore(struct device 
 
 #endif /* !CONFIG_HIBERNATION */
 
+#endif /* !CONFIG_PM_SLEEP */
+
+#ifdef CONFIG_PM_RUNTIME
+
+static int pci_pm_runtime_suspend(struct device *dev)
+{
+	struct pci_dev *pci_dev = to_pci_dev(dev);
+	const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+	pci_power_t prev = pci_dev->current_state;
+	int error;
+
+	if (!pm || !pm->runtime_suspend)
+		return -ENOSYS;
+
+	error = pm->runtime_suspend(dev);
+	suspend_report_result(pm->runtime_suspend, error);
+	if (error)
+		return error;
+
+	pci_fixup_device(pci_fixup_suspend, pci_dev);
+
+	if (!pci_dev->state_saved && pci_dev->current_state != PCI_D0
+	    && pci_dev->current_state != PCI_UNKNOWN) {
+		WARN_ONCE(pci_dev->current_state != prev,
+			"PCI PM: State of device not saved by %pF\n",
+			pm->runtime_suspend);
+		return 0;
+	}
+
+	if (!pci_dev->state_saved)
+		pci_save_state(pci_dev);
+
+	pci_finish_runtime_suspend(pci_dev);
+
+	return 0;
+}
+
+static int pci_pm_runtime_resume(struct device *dev)
+{
+	struct pci_dev *pci_dev = to_pci_dev(dev);
+	const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+
+	if (!pm || !pm->runtime_resume)
+		return -ENOSYS;
+
+	pci_pm_default_resume_early(pci_dev);
+	__pci_enable_wake(pci_dev, PCI_D0, true, false);
+	pci_fixup_device(pci_fixup_resume, pci_dev);
+
+	return pm->runtime_resume(dev);
+}
+
+static int pci_pm_runtime_idle(struct device *dev)
+{
+	const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+
+	if (!pm)
+		return -ENOSYS;
+
+	if (pm->runtime_idle) {
+		int ret = pm->runtime_idle(dev);
+		if (ret)
+			return ret;
+	}
+
+	pm_runtime_suspend(dev);
+
+	return 0;
+}
+
+#else /* !CONFIG_PM_RUNTIME */
+
+#define pci_pm_runtime_suspend	NULL
+#define pci_pm_runtime_resume	NULL
+#define pci_pm_runtime_idle	NULL
+
+#endif /* !CONFIG_PM_RUNTIME */
+
+#ifdef CONFIG_PM_OPS
+
 const struct dev_pm_ops pci_dev_pm_ops = {
 	.prepare = pci_pm_prepare,
 	.complete = pci_pm_complete,
@@ -946,15 +1038,18 @@ const 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,
+	.runtime_suspend = pci_pm_runtime_suspend,
+	.runtime_resume = pci_pm_runtime_resume,
+	.runtime_idle = pci_pm_runtime_idle,
 };
 
 #define PCI_PM_OPS_PTR	(&pci_dev_pm_ops)
 
-#else /* !CONFIG_PM_SLEEP */
+#else /* !COMFIG_PM_OPS */
 
 #define PCI_PM_OPS_PTR	NULL
 
-#endif /* !CONFIG_PM_SLEEP */
+#endif /* !COMFIG_PM_OPS */
 
 /**
  * __pci_register_driver - register a new pci driver
Index: linux-2.6/drivers/pci/pci.c
===================================================================
--- linux-2.6.orig/drivers/pci/pci.c
+++ linux-2.6/drivers/pci/pci.c
@@ -1287,9 +1287,10 @@ void pci_pme_active(struct pci_dev *dev,
 }
 
 /**
- * pci_enable_wake - enable PCI device as wakeup event source
+ * __pci_enable_wake - enable PCI device as wakeup event source
  * @dev: PCI device affected
  * @state: PCI state from which device will issue wakeup events
+ * @runtime: True if the events are to be generated at run time
  * @enable: True to enable event generation; false to disable
  *
  * This enables the device as a wakeup event source, or disables it.
@@ -1305,11 +1306,12 @@ void pci_pme_active(struct pci_dev *dev,
  * Error code depending on the platform is returned if both the platform and
  * the native mechanism fail to enable the generation of wake-up events
  */
-int pci_enable_wake(struct pci_dev *dev, pci_power_t state, bool enable)
+int __pci_enable_wake(struct pci_dev *dev, pci_power_t state,
+		      bool runtime, bool enable)
 {
 	int ret = 0;
 
-	if (enable && !device_may_wakeup(&dev->dev))
+	if (enable && !runtime && !device_may_wakeup(&dev->dev))
 		return -EINVAL;
 
 	/* Don't do the same thing twice in a row for one device. */
@@ -1329,19 +1331,24 @@ int pci_enable_wake(struct pci_dev *dev,
 			pci_pme_active(dev, true);
 		else
 			ret = 1;
-		error = platform_pci_sleep_wake(dev, true);
+		error = runtime ? platform_pci_run_wake(dev, true) :
+					platform_pci_sleep_wake(dev, true);
 		if (ret)
 			ret = error;
 		if (!ret)
 			dev->wakeup_prepared = true;
 	} else {
-		platform_pci_sleep_wake(dev, false);
+		if (runtime)
+			platform_pci_run_wake(dev, false);
+		else
+			platform_pci_sleep_wake(dev, false);
 		pci_pme_active(dev, false);
 		dev->wakeup_prepared = false;
 	}
 
 	return ret;
 }
+EXPORT_SYMBOL(__pci_enable_wake);
 
 /**
  * pci_wake_from_d3 - enable/disable device to wake up from D3_hot or D3_cold
@@ -1451,6 +1458,31 @@ int pci_back_from_sleep(struct pci_dev *
 }
 
 /**
+ * pci_finish_runtime_suspend - Carry out PCI-specific part of runtime suspend.
+ * @dev: PCI device being suspended.
+ *
+ * Prepare @dev to generate wake-up events at run time and put it into a low
+ * power state.
+ */
+int pci_finish_runtime_suspend(struct pci_dev *dev)
+{
+	pci_power_t target_state = pci_target_state(dev);
+	int error;
+
+	if (target_state == PCI_POWER_ERROR)
+		return -EIO;
+
+	__pci_enable_wake(dev, target_state, true, pci_dev_run_wake(dev));
+
+	error = pci_set_power_state(dev, target_state);
+
+	if (error)
+		__pci_enable_wake(dev, target_state, true, false);
+
+	return error;
+}
+
+/**
  * pci_dev_run_wake - Check if device can generate run-time wake-up events.
  * @dev: Device to check.
  *
@@ -2954,7 +2986,6 @@ EXPORT_SYMBOL(pci_save_state);
 EXPORT_SYMBOL(pci_restore_state);
 EXPORT_SYMBOL(pci_pme_capable);
 EXPORT_SYMBOL(pci_pme_active);
-EXPORT_SYMBOL(pci_enable_wake);
 EXPORT_SYMBOL(pci_wake_from_d3);
 EXPORT_SYMBOL(pci_target_state);
 EXPORT_SYMBOL(pci_prepare_to_sleep);
Index: linux-2.6/include/linux/pci.h
===================================================================
--- linux-2.6.orig/include/linux/pci.h
+++ linux-2.6/include/linux/pci.h
@@ -747,13 +747,20 @@ int pci_set_power_state(struct pci_dev *
 pci_power_t pci_choose_state(struct pci_dev *dev, pm_message_t state);
 bool pci_pme_capable(struct pci_dev *dev, pci_power_t state);
 void pci_pme_active(struct pci_dev *dev, bool enable);
-int pci_enable_wake(struct pci_dev *dev, pci_power_t state, bool enable);
+int __pci_enable_wake(struct pci_dev *dev, pci_power_t state,
+		      bool runtime, bool enable);
 int pci_wake_from_d3(struct pci_dev *dev, bool enable);
 pci_power_t pci_target_state(struct pci_dev *dev);
 int pci_prepare_to_sleep(struct pci_dev *dev);
 int pci_back_from_sleep(struct pci_dev *dev);
 bool pci_dev_run_wake(struct pci_dev *dev);
 
+static inline int pci_enable_wake(struct pci_dev *dev, pci_power_t state,
+				  bool enable)
+{
+	return __pci_enable_wake(dev, state, false, enable);
+}
+
 /* Functions for PCI Hotplug drivers to use */
 int pci_bus_find_capability(struct pci_bus *bus, unsigned int devfn, int cap);
 #ifdef CONFIG_HOTPLUG
Index: linux-2.6/drivers/pci/pci.h
===================================================================
--- linux-2.6.orig/drivers/pci/pci.h
+++ linux-2.6/drivers/pci/pci.h
@@ -55,6 +55,7 @@ extern int pci_set_platform_pm(struct pc
 extern void pci_update_current_state(struct pci_dev *dev, pci_power_t state);
 extern void pci_disable_enabled_device(struct pci_dev *dev);
 extern bool pci_check_pme_status(struct pci_dev *dev);
+extern int pci_finish_runtime_suspend(struct pci_dev *dev);
 extern int __pci_pme_wakeup(struct pci_dev *dev, void *ign);
 extern void pci_pme_wakeup_bus(struct pci_bus *bus);
 extern void pci_pm_init(struct pci_dev *dev);

^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH 12/12] PM / r8169: Add simplified run-time PM support
  2009-12-27 19:57 [PATCH 0/12] PCI run-time PM support (rev. 2) Rafael J. Wysocki
                   ` (19 preceding siblings ...)
  2009-12-27 20:11 ` [PATCH 12/12] PM / r8169: Add simplified run-time PM support Rafael J. Wysocki
@ 2009-12-27 20:11 ` Rafael J. Wysocki
  2010-01-01 19:06   ` [PATCH 12/12] PM / r8169: Add simplified run-time PM support (rev. 2) Rafael J. Wysocki
  2010-01-01 19:06   ` Rafael J. Wysocki
  2010-01-01  1:29 ` [PATCH][RFC] e1000e: Add basic runtime PM support (was: [PATCH 0/12] PCI run-time PM support (rev. 2)) Rafael J. Wysocki
  2010-01-01  1:29   ` Rafael J. Wysocki
  22 siblings, 2 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2009-12-27 20:11 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Matthew Garrett, Len Brown, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu

From: Rafael J. Wysocki <rjw@sisk.pl>

Use the PCI run-time power management framework to add simplified
run-time PM support to the r8169 driver.  Namely, make the driver
suspend the device when the link is off and set it up for generating
wake-up event after the link has been detected again.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
The patch is still missing an important part, which is that the driver should
turn the power management on as soon as it's probed, not only after someone
calls open() on it.  Nevertheless, the patch is fully functional in this form.

Rafael
---
 drivers/net/r8169.c |  135 +++++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 108 insertions(+), 27 deletions(-)

Index: linux-2.6/drivers/net/r8169.c
===================================================================
--- linux-2.6.orig/drivers/net/r8169.c
+++ linux-2.6/drivers/net/r8169.c
@@ -23,6 +23,7 @@
 #include <linux/tcp.h>
 #include <linux/init.h>
 #include <linux/dma-mapping.h>
+#include <linux/pm_runtime.h>
 
 #include <asm/system.h>
 #include <asm/io.h>
@@ -504,6 +505,8 @@ struct rtl8169_private {
 
 	struct mii_if_info mii;
 	struct rtl8169_counters counters;
+	u32 saved_wolopts;
+	bool exiting;
 };
 
 MODULE_AUTHOR("Realtek and the Linux r8169 crew <netdev@vger.kernel.org>");
@@ -743,6 +746,8 @@ static void rtl8169_check_link_status(st
 
 	spin_lock_irqsave(&tp->lock, flags);
 	if (tp->link_ok(ioaddr)) {
+		/* This is to cancel a scheduled suspend if there's one. */
+		pm_request_resume(&tp->pci_dev->dev);
 		netif_carrier_on(dev);
 		if (netif_msg_ifup(tp))
 			printk(KERN_INFO PFX "%s: link up\n", dev->name);
@@ -750,48 +755,54 @@ static void rtl8169_check_link_status(st
 		if (netif_msg_ifdown(tp))
 			printk(KERN_INFO PFX "%s: link down\n", dev->name);
 		netif_carrier_off(dev);
+		pm_schedule_suspend(&tp->pci_dev->dev, 100);
 	}
 	spin_unlock_irqrestore(&tp->lock, flags);
 }
 
-static void rtl8169_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
+#define WAKE_ANY (WAKE_PHY | WAKE_MAGIC | WAKE_UCAST | WAKE_BCAST | WAKE_MCAST)
+
+static u32 __rtl8169_get_wol(struct rtl8169_private *tp)
 {
-	struct rtl8169_private *tp = netdev_priv(dev);
 	void __iomem *ioaddr = tp->mmio_addr;
 	u8 options;
-
-	wol->wolopts = 0;
-
-#define WAKE_ANY (WAKE_PHY | WAKE_MAGIC | WAKE_UCAST | WAKE_BCAST | WAKE_MCAST)
-	wol->supported = WAKE_ANY;
-
-	spin_lock_irq(&tp->lock);
+	u32 wolopts = 0;
 
 	options = RTL_R8(Config1);
 	if (!(options & PMEnable))
-		goto out_unlock;
+		return 0;
 
 	options = RTL_R8(Config3);
 	if (options & LinkUp)
-		wol->wolopts |= WAKE_PHY;
+		wolopts |= WAKE_PHY;
 	if (options & MagicPacket)
-		wol->wolopts |= WAKE_MAGIC;
+		wolopts |= WAKE_MAGIC;
 
 	options = RTL_R8(Config5);
 	if (options & UWF)
-		wol->wolopts |= WAKE_UCAST;
+		wolopts |= WAKE_UCAST;
 	if (options & BWF)
-		wol->wolopts |= WAKE_BCAST;
+		wolopts |= WAKE_BCAST;
 	if (options & MWF)
-		wol->wolopts |= WAKE_MCAST;
+		wolopts |= WAKE_MCAST;
 
-out_unlock:
-	spin_unlock_irq(&tp->lock);
+	return wolopts;
 }
 
-static int rtl8169_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
+static void rtl8169_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
 {
 	struct rtl8169_private *tp = netdev_priv(dev);
+
+	spin_lock_irq(&tp->lock);
+
+	wol->supported = WAKE_ANY;
+	wol->wolopts = __rtl8169_get_wol(tp);
+
+	spin_unlock_irq(&tp->lock);
+}
+
+static void __rtl8169_set_wol(struct rtl8169_private *tp, u32 wolopts)
+{
 	void __iomem *ioaddr = tp->mmio_addr;
 	unsigned int i;
 	static const struct {
@@ -808,23 +819,29 @@ static int rtl8169_set_wol(struct net_de
 		{ WAKE_ANY,   Config5, LanWake }
 	};
 
-	spin_lock_irq(&tp->lock);
-
 	RTL_W8(Cfg9346, Cfg9346_Unlock);
 
 	for (i = 0; i < ARRAY_SIZE(cfg); i++) {
 		u8 options = RTL_R8(cfg[i].reg) & ~cfg[i].mask;
-		if (wol->wolopts & cfg[i].opt)
+		if (wolopts & cfg[i].opt)
 			options |= cfg[i].mask;
 		RTL_W8(cfg[i].reg, options);
 	}
 
 	RTL_W8(Cfg9346, Cfg9346_Lock);
+}
+
+static int rtl8169_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
+{
+	struct rtl8169_private *tp = netdev_priv(dev);
+
+	spin_lock_irq(&tp->lock);
 
 	if (wol->wolopts)
 		tp->features |= RTL_FEATURE_WOL;
 	else
 		tp->features &= ~RTL_FEATURE_WOL;
+	__rtl8169_set_wol(tp, wol->wolopts);
 	device_set_wakeup_enable(&tp->pci_dev->dev, wol->wolopts);
 
 	spin_unlock_irq(&tp->lock);
@@ -3000,6 +3017,7 @@ rtl8169_init_one(struct pci_dev *pdev, c
 	tp->dev = dev;
 	tp->pci_dev = pdev;
 	tp->msg_enable = netif_msg_init(debug.msg_enable, R8169_MSG_DEFAULT);
+	tp->exiting = false;
 
 	mii = &tp->mii;
 	mii->dev = dev;
@@ -3232,6 +3250,8 @@ static void __devexit rtl8169_remove_one
 	struct net_device *dev = pci_get_drvdata(pdev);
 	struct rtl8169_private *tp = netdev_priv(dev);
 
+	tp->exiting = true;
+
 	flush_scheduled_work();
 
 	unregister_netdev(dev);
@@ -3258,7 +3278,6 @@ static int rtl8169_open(struct net_devic
 	struct pci_dev *pdev = tp->pci_dev;
 	int retval = -ENOMEM;
 
-
 	rtl8169_set_rxbufsize(tp, dev);
 
 	/*
@@ -3295,6 +3314,13 @@ static int rtl8169_open(struct net_devic
 
 	rtl8169_request_timer(dev);
 
+	tp->saved_wolopts = 0;
+
+	if (pci_dev_run_wake(pdev)) {
+		pm_runtime_set_active(&pdev->dev);
+		pm_runtime_enable(&pdev->dev);
+	}
+
 	rtl8169_check_link_status(dev, tp, tp->mmio_addr);
 out:
 	return retval;
@@ -4727,6 +4753,14 @@ static int rtl8169_close(struct net_devi
 	struct rtl8169_private *tp = netdev_priv(dev);
 	struct pci_dev *pdev = tp->pci_dev;
 
+	if (pci_dev_run_wake(pdev)) {
+		pm_runtime_get_noresume(&pdev->dev);
+		pm_runtime_resume(&pdev->dev);
+		pm_runtime_disable(&pdev->dev);
+		pm_runtime_set_suspended(&pdev->dev);
+		pm_runtime_put_noidle(&pdev->dev);
+	}
+
 	/* update counters before going down */
 	rtl8169_update_counters(dev);
 
@@ -4844,21 +4878,65 @@ static int rtl8169_suspend(struct device
 	return 0;
 }
 
+static void __rtl8169_resume(struct net_device *dev)
+{
+	netif_device_attach(dev);
+	rtl8169_schedule_work(dev, rtl8169_reset_task);
+}
+
 static int rtl8169_resume(struct device *device)
 {
 	struct pci_dev *pdev = to_pci_dev(device);
 	struct net_device *dev = pci_get_drvdata(pdev);
 
-	if (!netif_running(dev))
-		goto out;
+	if (netif_running(dev))
+		__rtl8169_resume(dev);
 
-	netif_device_attach(dev);
+	return 0;
+}
+
+static int rtl8169_runtime_suspend(struct device *device)
+{
+	struct pci_dev *pdev = to_pci_dev(device);
+	struct net_device *dev = pci_get_drvdata(pdev);
+	struct rtl8169_private *tp = netdev_priv(dev);
+
+	dev_dbg(&pdev->dev, "suspending\n");
+
+	spin_lock_irq(&tp->lock);
+	tp->saved_wolopts = __rtl8169_get_wol(tp);
+	__rtl8169_set_wol(tp, WAKE_ANY);
+	spin_unlock_irq(&tp->lock);
+
+	rtl8169_net_suspend(dev);
+
+	return 0;
+}
+
+static int rtl8169_runtime_resume(struct device *device)
+{
+	struct pci_dev *pdev = to_pci_dev(device);
+	struct net_device *dev = pci_get_drvdata(pdev);
+	struct rtl8169_private *tp = netdev_priv(dev);
+
+	dev_dbg(&pdev->dev, "resuming\n");
+
+	spin_lock_irq(&tp->lock);
+	__rtl8169_set_wol(tp, tp->saved_wolopts);
+	tp->saved_wolopts = 0;
+	spin_unlock_irq(&tp->lock);
+
+	if (!tp->exiting)
+		__rtl8169_resume(dev);
 
-	rtl8169_schedule_work(dev, rtl8169_reset_task);
-out:
 	return 0;
 }
 
+static int rtl8169_runtime_idle(struct device *device)
+{
+	return -EBUSY;
+}
+
 static const struct dev_pm_ops rtl8169_pm_ops = {
 	.suspend = rtl8169_suspend,
 	.resume = rtl8169_resume,
@@ -4866,6 +4944,9 @@ static const struct dev_pm_ops rtl8169_p
 	.thaw = rtl8169_resume,
 	.poweroff = rtl8169_suspend,
 	.restore = rtl8169_resume,
+	.runtime_suspend = rtl8169_runtime_suspend,
+	.runtime_resume = rtl8169_runtime_resume,
+	.runtime_idle = rtl8169_runtime_idle,
 };
 
 #define RTL8169_PM_OPS	(&rtl8169_pm_ops)

^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH 12/12] PM / r8169: Add simplified run-time PM support
  2009-12-27 19:57 [PATCH 0/12] PCI run-time PM support (rev. 2) Rafael J. Wysocki
                   ` (18 preceding siblings ...)
  2009-12-27 20:08 ` Rafael J. Wysocki
@ 2009-12-27 20:11 ` Rafael J. Wysocki
  2009-12-27 20:11 ` Rafael J. Wysocki
                   ` (2 subsequent siblings)
  22 siblings, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2009-12-27 20:11 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Linux PCI, LKML, ACPI Devel Maling List, Francois Romieu,
	pm list, Matthew Garrett

From: Rafael J. Wysocki <rjw@sisk.pl>

Use the PCI run-time power management framework to add simplified
run-time PM support to the r8169 driver.  Namely, make the driver
suspend the device when the link is off and set it up for generating
wake-up event after the link has been detected again.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
The patch is still missing an important part, which is that the driver should
turn the power management on as soon as it's probed, not only after someone
calls open() on it.  Nevertheless, the patch is fully functional in this form.

Rafael
---
 drivers/net/r8169.c |  135 +++++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 108 insertions(+), 27 deletions(-)

Index: linux-2.6/drivers/net/r8169.c
===================================================================
--- linux-2.6.orig/drivers/net/r8169.c
+++ linux-2.6/drivers/net/r8169.c
@@ -23,6 +23,7 @@
 #include <linux/tcp.h>
 #include <linux/init.h>
 #include <linux/dma-mapping.h>
+#include <linux/pm_runtime.h>
 
 #include <asm/system.h>
 #include <asm/io.h>
@@ -504,6 +505,8 @@ struct rtl8169_private {
 
 	struct mii_if_info mii;
 	struct rtl8169_counters counters;
+	u32 saved_wolopts;
+	bool exiting;
 };
 
 MODULE_AUTHOR("Realtek and the Linux r8169 crew <netdev@vger.kernel.org>");
@@ -743,6 +746,8 @@ static void rtl8169_check_link_status(st
 
 	spin_lock_irqsave(&tp->lock, flags);
 	if (tp->link_ok(ioaddr)) {
+		/* This is to cancel a scheduled suspend if there's one. */
+		pm_request_resume(&tp->pci_dev->dev);
 		netif_carrier_on(dev);
 		if (netif_msg_ifup(tp))
 			printk(KERN_INFO PFX "%s: link up\n", dev->name);
@@ -750,48 +755,54 @@ static void rtl8169_check_link_status(st
 		if (netif_msg_ifdown(tp))
 			printk(KERN_INFO PFX "%s: link down\n", dev->name);
 		netif_carrier_off(dev);
+		pm_schedule_suspend(&tp->pci_dev->dev, 100);
 	}
 	spin_unlock_irqrestore(&tp->lock, flags);
 }
 
-static void rtl8169_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
+#define WAKE_ANY (WAKE_PHY | WAKE_MAGIC | WAKE_UCAST | WAKE_BCAST | WAKE_MCAST)
+
+static u32 __rtl8169_get_wol(struct rtl8169_private *tp)
 {
-	struct rtl8169_private *tp = netdev_priv(dev);
 	void __iomem *ioaddr = tp->mmio_addr;
 	u8 options;
-
-	wol->wolopts = 0;
-
-#define WAKE_ANY (WAKE_PHY | WAKE_MAGIC | WAKE_UCAST | WAKE_BCAST | WAKE_MCAST)
-	wol->supported = WAKE_ANY;
-
-	spin_lock_irq(&tp->lock);
+	u32 wolopts = 0;
 
 	options = RTL_R8(Config1);
 	if (!(options & PMEnable))
-		goto out_unlock;
+		return 0;
 
 	options = RTL_R8(Config3);
 	if (options & LinkUp)
-		wol->wolopts |= WAKE_PHY;
+		wolopts |= WAKE_PHY;
 	if (options & MagicPacket)
-		wol->wolopts |= WAKE_MAGIC;
+		wolopts |= WAKE_MAGIC;
 
 	options = RTL_R8(Config5);
 	if (options & UWF)
-		wol->wolopts |= WAKE_UCAST;
+		wolopts |= WAKE_UCAST;
 	if (options & BWF)
-		wol->wolopts |= WAKE_BCAST;
+		wolopts |= WAKE_BCAST;
 	if (options & MWF)
-		wol->wolopts |= WAKE_MCAST;
+		wolopts |= WAKE_MCAST;
 
-out_unlock:
-	spin_unlock_irq(&tp->lock);
+	return wolopts;
 }
 
-static int rtl8169_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
+static void rtl8169_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
 {
 	struct rtl8169_private *tp = netdev_priv(dev);
+
+	spin_lock_irq(&tp->lock);
+
+	wol->supported = WAKE_ANY;
+	wol->wolopts = __rtl8169_get_wol(tp);
+
+	spin_unlock_irq(&tp->lock);
+}
+
+static void __rtl8169_set_wol(struct rtl8169_private *tp, u32 wolopts)
+{
 	void __iomem *ioaddr = tp->mmio_addr;
 	unsigned int i;
 	static const struct {
@@ -808,23 +819,29 @@ static int rtl8169_set_wol(struct net_de
 		{ WAKE_ANY,   Config5, LanWake }
 	};
 
-	spin_lock_irq(&tp->lock);
-
 	RTL_W8(Cfg9346, Cfg9346_Unlock);
 
 	for (i = 0; i < ARRAY_SIZE(cfg); i++) {
 		u8 options = RTL_R8(cfg[i].reg) & ~cfg[i].mask;
-		if (wol->wolopts & cfg[i].opt)
+		if (wolopts & cfg[i].opt)
 			options |= cfg[i].mask;
 		RTL_W8(cfg[i].reg, options);
 	}
 
 	RTL_W8(Cfg9346, Cfg9346_Lock);
+}
+
+static int rtl8169_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
+{
+	struct rtl8169_private *tp = netdev_priv(dev);
+
+	spin_lock_irq(&tp->lock);
 
 	if (wol->wolopts)
 		tp->features |= RTL_FEATURE_WOL;
 	else
 		tp->features &= ~RTL_FEATURE_WOL;
+	__rtl8169_set_wol(tp, wol->wolopts);
 	device_set_wakeup_enable(&tp->pci_dev->dev, wol->wolopts);
 
 	spin_unlock_irq(&tp->lock);
@@ -3000,6 +3017,7 @@ rtl8169_init_one(struct pci_dev *pdev, c
 	tp->dev = dev;
 	tp->pci_dev = pdev;
 	tp->msg_enable = netif_msg_init(debug.msg_enable, R8169_MSG_DEFAULT);
+	tp->exiting = false;
 
 	mii = &tp->mii;
 	mii->dev = dev;
@@ -3232,6 +3250,8 @@ static void __devexit rtl8169_remove_one
 	struct net_device *dev = pci_get_drvdata(pdev);
 	struct rtl8169_private *tp = netdev_priv(dev);
 
+	tp->exiting = true;
+
 	flush_scheduled_work();
 
 	unregister_netdev(dev);
@@ -3258,7 +3278,6 @@ static int rtl8169_open(struct net_devic
 	struct pci_dev *pdev = tp->pci_dev;
 	int retval = -ENOMEM;
 
-
 	rtl8169_set_rxbufsize(tp, dev);
 
 	/*
@@ -3295,6 +3314,13 @@ static int rtl8169_open(struct net_devic
 
 	rtl8169_request_timer(dev);
 
+	tp->saved_wolopts = 0;
+
+	if (pci_dev_run_wake(pdev)) {
+		pm_runtime_set_active(&pdev->dev);
+		pm_runtime_enable(&pdev->dev);
+	}
+
 	rtl8169_check_link_status(dev, tp, tp->mmio_addr);
 out:
 	return retval;
@@ -4727,6 +4753,14 @@ static int rtl8169_close(struct net_devi
 	struct rtl8169_private *tp = netdev_priv(dev);
 	struct pci_dev *pdev = tp->pci_dev;
 
+	if (pci_dev_run_wake(pdev)) {
+		pm_runtime_get_noresume(&pdev->dev);
+		pm_runtime_resume(&pdev->dev);
+		pm_runtime_disable(&pdev->dev);
+		pm_runtime_set_suspended(&pdev->dev);
+		pm_runtime_put_noidle(&pdev->dev);
+	}
+
 	/* update counters before going down */
 	rtl8169_update_counters(dev);
 
@@ -4844,21 +4878,65 @@ static int rtl8169_suspend(struct device
 	return 0;
 }
 
+static void __rtl8169_resume(struct net_device *dev)
+{
+	netif_device_attach(dev);
+	rtl8169_schedule_work(dev, rtl8169_reset_task);
+}
+
 static int rtl8169_resume(struct device *device)
 {
 	struct pci_dev *pdev = to_pci_dev(device);
 	struct net_device *dev = pci_get_drvdata(pdev);
 
-	if (!netif_running(dev))
-		goto out;
+	if (netif_running(dev))
+		__rtl8169_resume(dev);
 
-	netif_device_attach(dev);
+	return 0;
+}
+
+static int rtl8169_runtime_suspend(struct device *device)
+{
+	struct pci_dev *pdev = to_pci_dev(device);
+	struct net_device *dev = pci_get_drvdata(pdev);
+	struct rtl8169_private *tp = netdev_priv(dev);
+
+	dev_dbg(&pdev->dev, "suspending\n");
+
+	spin_lock_irq(&tp->lock);
+	tp->saved_wolopts = __rtl8169_get_wol(tp);
+	__rtl8169_set_wol(tp, WAKE_ANY);
+	spin_unlock_irq(&tp->lock);
+
+	rtl8169_net_suspend(dev);
+
+	return 0;
+}
+
+static int rtl8169_runtime_resume(struct device *device)
+{
+	struct pci_dev *pdev = to_pci_dev(device);
+	struct net_device *dev = pci_get_drvdata(pdev);
+	struct rtl8169_private *tp = netdev_priv(dev);
+
+	dev_dbg(&pdev->dev, "resuming\n");
+
+	spin_lock_irq(&tp->lock);
+	__rtl8169_set_wol(tp, tp->saved_wolopts);
+	tp->saved_wolopts = 0;
+	spin_unlock_irq(&tp->lock);
+
+	if (!tp->exiting)
+		__rtl8169_resume(dev);
 
-	rtl8169_schedule_work(dev, rtl8169_reset_task);
-out:
 	return 0;
 }
 
+static int rtl8169_runtime_idle(struct device *device)
+{
+	return -EBUSY;
+}
+
 static const struct dev_pm_ops rtl8169_pm_ops = {
 	.suspend = rtl8169_suspend,
 	.resume = rtl8169_resume,
@@ -4866,6 +4944,9 @@ static const struct dev_pm_ops rtl8169_p
 	.thaw = rtl8169_resume,
 	.poweroff = rtl8169_suspend,
 	.restore = rtl8169_resume,
+	.runtime_suspend = rtl8169_runtime_suspend,
+	.runtime_resume = rtl8169_runtime_resume,
+	.runtime_idle = rtl8169_runtime_idle,
 };
 
 #define RTL8169_PM_OPS	(&rtl8169_pm_ops)

^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH][RFC] e1000e: Add basic runtime PM support (was: [PATCH 0/12] PCI run-time PM support (rev. 2))
  2009-12-27 19:57 [PATCH 0/12] PCI run-time PM support (rev. 2) Rafael J. Wysocki
@ 2010-01-01  1:29   ` Rafael J. Wysocki
  2009-12-27 20:00 ` [PATCH 2/12] PCI / PM: Propagate wake-up enable for PCIe devices too Rafael J. Wysocki
                     ` (21 subsequent siblings)
  22 siblings, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2010-01-01  1:29 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: e1000-devel, Linux PCI, Oliver Neukum, LKML, Bjorn Helgaas,
	ACPI Devel Maling List, Alan Stern, Jeff Kirsher,
	Francois Romieu, Shaohua Li, pm list, Matthew Garrett, Len Brown

On Sunday 27 December 2009, Rafael J. Wysocki wrote:
> Hi,
> 
> The following (updated) series of patches provides preliminary run-time power
> management support for PCI devices through ACPI and/or the native PCIe PME.
> 
> Some patches have been modified since the previous iteration, one patch has
> been merged and there's one more.
> 
> I've tested this patchset with the native PCIe PME mechanism using the r8169
> driver on the MSI Wind U-100 (see the last patch for details) and with the ACPI
> mechanism using the e1000e driver on the Toshiba Portege R500 (the patch still
> requires some work to be shown in public ;-)).

The e1000e patch is now in a better shape IMO, at least it worked for me in all
conditions I tested it, so it is appended below.

> [1/12] - Add function for checking PME status of devices
>  
> [2/12] - Modify wake-up enable propagation so that it's done for PCIe devices
>          too (this one is in the Jesse's tree already, but it's reproduced here
>          for completness)
>  
> [3/12] - PCIe PME root port service driver
> 
> [4/12] - "Don't use MSIs for PME signaling" switch for PCIe
>  
> [5/12] - ACPI GPE refcounting, from Matthew
>  
> [6/12] - ACPI drivers support for GPE refcounting, from Matthew
>  
> [7/12] - ACPI removal of the old GPE API, from Matthew
>  
> [8/12] - ACPI add fields for handling run-wake devices
>  
> [9/12][New] - ACPI add helper function for enabling/disabling wake-up power
> 
> [10/12] - PCI / ACPI platform support for run-time power management
> 
> [11/12] - Runtime PM callbacks for the PCI bus type
> 
> [12/12] - Runtime PM support for r8169 (experimental)
> 
> If there are no objections, I think the patches [1-11/12] are ready for the
> upstream.

---
From: Rafael J. Wysocki <rjw@sisk.pl>
Subject: e1000e: Add basic runtime PM support

Add support for suspending the device on link removal and resuming it
on link gain.

Based on a patch from Matthew Garrett.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
 drivers/net/e1000e/netdev.c |   90 ++++++++++++++++++++++++++++++++------------
 1 file changed, 66 insertions(+), 24 deletions(-)

Index: linux-2.6/drivers/net/e1000e/netdev.c
===================================================================
--- linux-2.6.orig/drivers/net/e1000e/netdev.c
+++ linux-2.6/drivers/net/e1000e/netdev.c
@@ -44,6 +44,7 @@
 #include <linux/cpu.h>
 #include <linux/smp.h>
 #include <linux/pm_qos_params.h>
+#include <linux/pm_runtime.h>
 #include <linux/aer.h>
 
 #include "e1000.h"
@@ -3093,12 +3094,18 @@ static int e1000_open(struct net_device 
 {
 	struct e1000_adapter *adapter = netdev_priv(netdev);
 	struct e1000_hw *hw = &adapter->hw;
+	struct pci_dev *pdev = adapter->pdev;
 	int err;
 
 	/* disallow open during test */
 	if (test_bit(__E1000_TESTING, &adapter->state))
 		return -EBUSY;
 
+	if (pci_dev_run_wake(pdev)) {
+		pm_runtime_set_active(&pdev->dev);
+		pm_runtime_enable(&pdev->dev);
+	}
+
 	netif_carrier_off(netdev);
 
 	/* allocate transmit descriptors */
@@ -3190,9 +3197,21 @@ err_setup_tx:
 static int e1000_close(struct net_device *netdev)
 {
 	struct e1000_adapter *adapter = netdev_priv(netdev);
+	struct pci_dev *pdev = adapter->pdev;
 
 	WARN_ON(test_bit(__E1000_RESETTING, &adapter->state));
-	e1000e_down(adapter);
+
+	if (pci_dev_run_wake(pdev)) {
+		pm_runtime_get_noresume(&pdev->dev);
+		pm_runtime_resume(&pdev->dev);
+		pm_runtime_disable(&pdev->dev);
+		pm_runtime_set_suspended(&pdev->dev);
+		pm_runtime_put_noidle(&pdev->dev);
+	}
+
+	if (netif_running(netdev))
+		e1000e_down(adapter);
+
 	e1000_power_down_phy(adapter);
 	e1000_free_irq(adapter);
 
@@ -3571,6 +3590,10 @@ static void e1000_watchdog_task(struct w
 	if (link) {
 		if (!netif_carrier_ok(netdev)) {
 			bool txb2b = 1;
+
+			/* This is to cancel scheduled suspend requests. */
+			pm_runtime_resume(netdev->dev.parent);
+
 			/* update snapshot of PHY registers on LSC */
 			e1000_phy_read_status(adapter);
 			mac->ops.get_link_up_info(&adapter->hw,
@@ -3686,6 +3709,8 @@ static void e1000_watchdog_task(struct w
 
 			if (adapter->flags & FLAG_RX_NEEDS_RESTART)
 				schedule_work(&adapter->reset_task);
+			else
+				pm_schedule_suspend(netdev->dev.parent, 100);
 		}
 	}
 
@@ -4489,13 +4514,15 @@ out:
 	return retval;
 }
 
-static int __e1000_shutdown(struct pci_dev *pdev, bool *enable_wake)
+static int __e1000_shutdown(struct pci_dev *pdev, bool *enable_wake,
+			    bool runtime)
 {
 	struct net_device *netdev = pci_get_drvdata(pdev);
 	struct e1000_adapter *adapter = netdev_priv(netdev);
 	struct e1000_hw *hw = &adapter->hw;
 	u32 ctrl, ctrl_ext, rctl, status;
-	u32 wufc = adapter->wol;
+	/* Runtime suspend should only enable wakeup for link changes */
+	u32 wufc = runtime ? E1000_WUFC_LNKC : adapter->wol;
 	int retval = 0;
 
 	netif_device_detach(netdev);
@@ -4653,41 +4680,43 @@ static void e1000e_disable_l1aspm(struct
 }
 
 #ifdef CONFIG_PM
-static int e1000_suspend(struct pci_dev *pdev, pm_message_t state)
+static int e1000_idle(struct device *dev)
 {
+	/* Prevent the driver core from changing our state */
+	return -EBUSY;
+}
+
+static int e1000_suspend(struct device *dev)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
 	int retval;
 	bool wake;
 
-	retval = __e1000_shutdown(pdev, &wake);
+	retval = __e1000_shutdown(pdev, &wake, false);
 	if (!retval)
 		e1000_complete_shutdown(pdev, true, wake);
 
 	return retval;
 }
 
-static int e1000_resume(struct pci_dev *pdev)
+static int e1000_runtime_suspend(struct device *dev)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
+	bool wake;
+
+	return __e1000_shutdown(pdev, &wake, true);
+}
+
+static int e1000_resume(struct device *dev)
 {
+	struct pci_dev *pdev = to_pci_dev(dev);
 	struct net_device *netdev = pci_get_drvdata(pdev);
 	struct e1000_adapter *adapter = netdev_priv(netdev);
 	struct e1000_hw *hw = &adapter->hw;
 	u32 err;
 
-	pci_set_power_state(pdev, PCI_D0);
-	pci_restore_state(pdev);
 	e1000e_disable_l1aspm(pdev);
 
-	err = pci_enable_device_mem(pdev);
-	if (err) {
-		dev_err(&pdev->dev,
-			"Cannot enable PCI device from suspend\n");
-		return err;
-	}
-
-	pci_set_master(pdev);
-
-	pci_enable_wake(pdev, PCI_D3hot, 0);
-	pci_enable_wake(pdev, PCI_D3cold, 0);
-
 	e1000e_set_interrupt_capability(adapter);
 	if (netif_running(netdev)) {
 		err = e1000_request_irq(adapter);
@@ -4751,7 +4780,7 @@ static void e1000_shutdown(struct pci_de
 {
 	bool wake = false;
 
-	__e1000_shutdown(pdev, &wake);
+	__e1000_shutdown(pdev, &wake, false);
 
 	if (system_state == SYSTEM_POWER_OFF)
 		e1000_complete_shutdown(pdev, false, wake);
@@ -5274,6 +5303,9 @@ static void __devexit e1000_remove(struc
 	struct net_device *netdev = pci_get_drvdata(pdev);
 	struct e1000_adapter *adapter = netdev_priv(netdev);
 
+	if (pci_dev_run_wake(pdev))
+		pm_runtime_resume(&pdev->dev);
+
 	/*
 	 * flush_scheduled work may reschedule our watchdog task, so
 	 * explicitly disable watchdog tasks from being rescheduled
@@ -5393,6 +5425,18 @@ static struct pci_device_id e1000_pci_tb
 };
 MODULE_DEVICE_TABLE(pci, e1000_pci_tbl);
 
+static struct dev_pm_ops e1000_pm_ops = {
+	.suspend  = e1000_suspend,
+	.resume   = e1000_resume,
+	.freeze = e1000_suspend,
+	.thaw = e1000_resume,
+	.poweroff = e1000_suspend,
+	.restore = e1000_resume,
+	.runtime_suspend = e1000_runtime_suspend,
+	.runtime_resume = e1000_resume,
+	.runtime_idle = e1000_idle,
+};
+
 /* PCI Device API Driver */
 static struct pci_driver e1000_driver = {
 	.name     = e1000e_driver_name,
@@ -5400,9 +5444,7 @@ static struct pci_driver e1000_driver = 
 	.probe    = e1000_probe,
 	.remove   = __devexit_p(e1000_remove),
 #ifdef CONFIG_PM
-	/* Power Management Hooks */
-	.suspend  = e1000_suspend,
-	.resume   = e1000_resume,
+	.driver.pm = &e1000_pm_ops,
 #endif
 	.shutdown = e1000_shutdown,
 	.err_handler = &e1000_err_handler

------------------------------------------------------------------------------
This SF.Net email is sponsored by the Verizon Developer Community
Take advantage of Verizon's best-in-class app development support
A streamlined, 14 day to market process makes app distribution fast and easy
Join now and get one step closer to millions of Verizon customers
http://p.sf.net/sfu/verizon-dev2dev 

^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH][RFC] e1000e: Add basic runtime PM support (was: [PATCH 0/12] PCI run-time PM support (rev. 2))
@ 2010-01-01  1:29   ` Rafael J. Wysocki
  0 siblings, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2010-01-01  1:29 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Matthew Garrett, Len Brown, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu, Jeff Kirsher, e1000-devel

On Sunday 27 December 2009, Rafael J. Wysocki wrote:
> Hi,
> 
> The following (updated) series of patches provides preliminary run-time power
> management support for PCI devices through ACPI and/or the native PCIe PME.
> 
> Some patches have been modified since the previous iteration, one patch has
> been merged and there's one more.
> 
> I've tested this patchset with the native PCIe PME mechanism using the r8169
> driver on the MSI Wind U-100 (see the last patch for details) and with the ACPI
> mechanism using the e1000e driver on the Toshiba Portege R500 (the patch still
> requires some work to be shown in public ;-)).

The e1000e patch is now in a better shape IMO, at least it worked for me in all
conditions I tested it, so it is appended below.

> [1/12] - Add function for checking PME status of devices
>  
> [2/12] - Modify wake-up enable propagation so that it's done for PCIe devices
>          too (this one is in the Jesse's tree already, but it's reproduced here
>          for completness)
>  
> [3/12] - PCIe PME root port service driver
> 
> [4/12] - "Don't use MSIs for PME signaling" switch for PCIe
>  
> [5/12] - ACPI GPE refcounting, from Matthew
>  
> [6/12] - ACPI drivers support for GPE refcounting, from Matthew
>  
> [7/12] - ACPI removal of the old GPE API, from Matthew
>  
> [8/12] - ACPI add fields for handling run-wake devices
>  
> [9/12][New] - ACPI add helper function for enabling/disabling wake-up power
> 
> [10/12] - PCI / ACPI platform support for run-time power management
> 
> [11/12] - Runtime PM callbacks for the PCI bus type
> 
> [12/12] - Runtime PM support for r8169 (experimental)
> 
> If there are no objections, I think the patches [1-11/12] are ready for the
> upstream.

---
From: Rafael J. Wysocki <rjw@sisk.pl>
Subject: e1000e: Add basic runtime PM support

Add support for suspending the device on link removal and resuming it
on link gain.

Based on a patch from Matthew Garrett.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
 drivers/net/e1000e/netdev.c |   90 ++++++++++++++++++++++++++++++++------------
 1 file changed, 66 insertions(+), 24 deletions(-)

Index: linux-2.6/drivers/net/e1000e/netdev.c
===================================================================
--- linux-2.6.orig/drivers/net/e1000e/netdev.c
+++ linux-2.6/drivers/net/e1000e/netdev.c
@@ -44,6 +44,7 @@
 #include <linux/cpu.h>
 #include <linux/smp.h>
 #include <linux/pm_qos_params.h>
+#include <linux/pm_runtime.h>
 #include <linux/aer.h>
 
 #include "e1000.h"
@@ -3093,12 +3094,18 @@ static int e1000_open(struct net_device 
 {
 	struct e1000_adapter *adapter = netdev_priv(netdev);
 	struct e1000_hw *hw = &adapter->hw;
+	struct pci_dev *pdev = adapter->pdev;
 	int err;
 
 	/* disallow open during test */
 	if (test_bit(__E1000_TESTING, &adapter->state))
 		return -EBUSY;
 
+	if (pci_dev_run_wake(pdev)) {
+		pm_runtime_set_active(&pdev->dev);
+		pm_runtime_enable(&pdev->dev);
+	}
+
 	netif_carrier_off(netdev);
 
 	/* allocate transmit descriptors */
@@ -3190,9 +3197,21 @@ err_setup_tx:
 static int e1000_close(struct net_device *netdev)
 {
 	struct e1000_adapter *adapter = netdev_priv(netdev);
+	struct pci_dev *pdev = adapter->pdev;
 
 	WARN_ON(test_bit(__E1000_RESETTING, &adapter->state));
-	e1000e_down(adapter);
+
+	if (pci_dev_run_wake(pdev)) {
+		pm_runtime_get_noresume(&pdev->dev);
+		pm_runtime_resume(&pdev->dev);
+		pm_runtime_disable(&pdev->dev);
+		pm_runtime_set_suspended(&pdev->dev);
+		pm_runtime_put_noidle(&pdev->dev);
+	}
+
+	if (netif_running(netdev))
+		e1000e_down(adapter);
+
 	e1000_power_down_phy(adapter);
 	e1000_free_irq(adapter);
 
@@ -3571,6 +3590,10 @@ static void e1000_watchdog_task(struct w
 	if (link) {
 		if (!netif_carrier_ok(netdev)) {
 			bool txb2b = 1;
+
+			/* This is to cancel scheduled suspend requests. */
+			pm_runtime_resume(netdev->dev.parent);
+
 			/* update snapshot of PHY registers on LSC */
 			e1000_phy_read_status(adapter);
 			mac->ops.get_link_up_info(&adapter->hw,
@@ -3686,6 +3709,8 @@ static void e1000_watchdog_task(struct w
 
 			if (adapter->flags & FLAG_RX_NEEDS_RESTART)
 				schedule_work(&adapter->reset_task);
+			else
+				pm_schedule_suspend(netdev->dev.parent, 100);
 		}
 	}
 
@@ -4489,13 +4514,15 @@ out:
 	return retval;
 }
 
-static int __e1000_shutdown(struct pci_dev *pdev, bool *enable_wake)
+static int __e1000_shutdown(struct pci_dev *pdev, bool *enable_wake,
+			    bool runtime)
 {
 	struct net_device *netdev = pci_get_drvdata(pdev);
 	struct e1000_adapter *adapter = netdev_priv(netdev);
 	struct e1000_hw *hw = &adapter->hw;
 	u32 ctrl, ctrl_ext, rctl, status;
-	u32 wufc = adapter->wol;
+	/* Runtime suspend should only enable wakeup for link changes */
+	u32 wufc = runtime ? E1000_WUFC_LNKC : adapter->wol;
 	int retval = 0;
 
 	netif_device_detach(netdev);
@@ -4653,41 +4680,43 @@ static void e1000e_disable_l1aspm(struct
 }
 
 #ifdef CONFIG_PM
-static int e1000_suspend(struct pci_dev *pdev, pm_message_t state)
+static int e1000_idle(struct device *dev)
 {
+	/* Prevent the driver core from changing our state */
+	return -EBUSY;
+}
+
+static int e1000_suspend(struct device *dev)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
 	int retval;
 	bool wake;
 
-	retval = __e1000_shutdown(pdev, &wake);
+	retval = __e1000_shutdown(pdev, &wake, false);
 	if (!retval)
 		e1000_complete_shutdown(pdev, true, wake);
 
 	return retval;
 }
 
-static int e1000_resume(struct pci_dev *pdev)
+static int e1000_runtime_suspend(struct device *dev)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
+	bool wake;
+
+	return __e1000_shutdown(pdev, &wake, true);
+}
+
+static int e1000_resume(struct device *dev)
 {
+	struct pci_dev *pdev = to_pci_dev(dev);
 	struct net_device *netdev = pci_get_drvdata(pdev);
 	struct e1000_adapter *adapter = netdev_priv(netdev);
 	struct e1000_hw *hw = &adapter->hw;
 	u32 err;
 
-	pci_set_power_state(pdev, PCI_D0);
-	pci_restore_state(pdev);
 	e1000e_disable_l1aspm(pdev);
 
-	err = pci_enable_device_mem(pdev);
-	if (err) {
-		dev_err(&pdev->dev,
-			"Cannot enable PCI device from suspend\n");
-		return err;
-	}
-
-	pci_set_master(pdev);
-
-	pci_enable_wake(pdev, PCI_D3hot, 0);
-	pci_enable_wake(pdev, PCI_D3cold, 0);
-
 	e1000e_set_interrupt_capability(adapter);
 	if (netif_running(netdev)) {
 		err = e1000_request_irq(adapter);
@@ -4751,7 +4780,7 @@ static void e1000_shutdown(struct pci_de
 {
 	bool wake = false;
 
-	__e1000_shutdown(pdev, &wake);
+	__e1000_shutdown(pdev, &wake, false);
 
 	if (system_state == SYSTEM_POWER_OFF)
 		e1000_complete_shutdown(pdev, false, wake);
@@ -5274,6 +5303,9 @@ static void __devexit e1000_remove(struc
 	struct net_device *netdev = pci_get_drvdata(pdev);
 	struct e1000_adapter *adapter = netdev_priv(netdev);
 
+	if (pci_dev_run_wake(pdev))
+		pm_runtime_resume(&pdev->dev);
+
 	/*
 	 * flush_scheduled work may reschedule our watchdog task, so
 	 * explicitly disable watchdog tasks from being rescheduled
@@ -5393,6 +5425,18 @@ static struct pci_device_id e1000_pci_tb
 };
 MODULE_DEVICE_TABLE(pci, e1000_pci_tbl);
 
+static struct dev_pm_ops e1000_pm_ops = {
+	.suspend  = e1000_suspend,
+	.resume   = e1000_resume,
+	.freeze = e1000_suspend,
+	.thaw = e1000_resume,
+	.poweroff = e1000_suspend,
+	.restore = e1000_resume,
+	.runtime_suspend = e1000_runtime_suspend,
+	.runtime_resume = e1000_resume,
+	.runtime_idle = e1000_idle,
+};
+
 /* PCI Device API Driver */
 static struct pci_driver e1000_driver = {
 	.name     = e1000e_driver_name,
@@ -5400,9 +5444,7 @@ static struct pci_driver e1000_driver = 
 	.probe    = e1000_probe,
 	.remove   = __devexit_p(e1000_remove),
 #ifdef CONFIG_PM
-	/* Power Management Hooks */
-	.suspend  = e1000_suspend,
-	.resume   = e1000_resume,
+	.driver.pm = &e1000_pm_ops,
 #endif
 	.shutdown = e1000_shutdown,
 	.err_handler = &e1000_err_handler

^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH][RFC] e1000e: Add basic runtime PM support (was: [PATCH 0/12] PCI run-time PM support (rev. 2))
  2009-12-27 19:57 [PATCH 0/12] PCI run-time PM support (rev. 2) Rafael J. Wysocki
                   ` (20 preceding siblings ...)
  2009-12-27 20:11 ` Rafael J. Wysocki
@ 2010-01-01  1:29 ` Rafael J. Wysocki
  2010-01-01  1:29   ` Rafael J. Wysocki
  22 siblings, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2010-01-01  1:29 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: e1000-devel, Linux PCI, LKML, ACPI Devel Maling List,
	Jeff Kirsher, Francois Romieu, pm list, Matthew Garrett

On Sunday 27 December 2009, Rafael J. Wysocki wrote:
> Hi,
> 
> The following (updated) series of patches provides preliminary run-time power
> management support for PCI devices through ACPI and/or the native PCIe PME.
> 
> Some patches have been modified since the previous iteration, one patch has
> been merged and there's one more.
> 
> I've tested this patchset with the native PCIe PME mechanism using the r8169
> driver on the MSI Wind U-100 (see the last patch for details) and with the ACPI
> mechanism using the e1000e driver on the Toshiba Portege R500 (the patch still
> requires some work to be shown in public ;-)).

The e1000e patch is now in a better shape IMO, at least it worked for me in all
conditions I tested it, so it is appended below.

> [1/12] - Add function for checking PME status of devices
>  
> [2/12] - Modify wake-up enable propagation so that it's done for PCIe devices
>          too (this one is in the Jesse's tree already, but it's reproduced here
>          for completness)
>  
> [3/12] - PCIe PME root port service driver
> 
> [4/12] - "Don't use MSIs for PME signaling" switch for PCIe
>  
> [5/12] - ACPI GPE refcounting, from Matthew
>  
> [6/12] - ACPI drivers support for GPE refcounting, from Matthew
>  
> [7/12] - ACPI removal of the old GPE API, from Matthew
>  
> [8/12] - ACPI add fields for handling run-wake devices
>  
> [9/12][New] - ACPI add helper function for enabling/disabling wake-up power
> 
> [10/12] - PCI / ACPI platform support for run-time power management
> 
> [11/12] - Runtime PM callbacks for the PCI bus type
> 
> [12/12] - Runtime PM support for r8169 (experimental)
> 
> If there are no objections, I think the patches [1-11/12] are ready for the
> upstream.

---
From: Rafael J. Wysocki <rjw@sisk.pl>
Subject: e1000e: Add basic runtime PM support

Add support for suspending the device on link removal and resuming it
on link gain.

Based on a patch from Matthew Garrett.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
 drivers/net/e1000e/netdev.c |   90 ++++++++++++++++++++++++++++++++------------
 1 file changed, 66 insertions(+), 24 deletions(-)

Index: linux-2.6/drivers/net/e1000e/netdev.c
===================================================================
--- linux-2.6.orig/drivers/net/e1000e/netdev.c
+++ linux-2.6/drivers/net/e1000e/netdev.c
@@ -44,6 +44,7 @@
 #include <linux/cpu.h>
 #include <linux/smp.h>
 #include <linux/pm_qos_params.h>
+#include <linux/pm_runtime.h>
 #include <linux/aer.h>
 
 #include "e1000.h"
@@ -3093,12 +3094,18 @@ static int e1000_open(struct net_device 
 {
 	struct e1000_adapter *adapter = netdev_priv(netdev);
 	struct e1000_hw *hw = &adapter->hw;
+	struct pci_dev *pdev = adapter->pdev;
 	int err;
 
 	/* disallow open during test */
 	if (test_bit(__E1000_TESTING, &adapter->state))
 		return -EBUSY;
 
+	if (pci_dev_run_wake(pdev)) {
+		pm_runtime_set_active(&pdev->dev);
+		pm_runtime_enable(&pdev->dev);
+	}
+
 	netif_carrier_off(netdev);
 
 	/* allocate transmit descriptors */
@@ -3190,9 +3197,21 @@ err_setup_tx:
 static int e1000_close(struct net_device *netdev)
 {
 	struct e1000_adapter *adapter = netdev_priv(netdev);
+	struct pci_dev *pdev = adapter->pdev;
 
 	WARN_ON(test_bit(__E1000_RESETTING, &adapter->state));
-	e1000e_down(adapter);
+
+	if (pci_dev_run_wake(pdev)) {
+		pm_runtime_get_noresume(&pdev->dev);
+		pm_runtime_resume(&pdev->dev);
+		pm_runtime_disable(&pdev->dev);
+		pm_runtime_set_suspended(&pdev->dev);
+		pm_runtime_put_noidle(&pdev->dev);
+	}
+
+	if (netif_running(netdev))
+		e1000e_down(adapter);
+
 	e1000_power_down_phy(adapter);
 	e1000_free_irq(adapter);
 
@@ -3571,6 +3590,10 @@ static void e1000_watchdog_task(struct w
 	if (link) {
 		if (!netif_carrier_ok(netdev)) {
 			bool txb2b = 1;
+
+			/* This is to cancel scheduled suspend requests. */
+			pm_runtime_resume(netdev->dev.parent);
+
 			/* update snapshot of PHY registers on LSC */
 			e1000_phy_read_status(adapter);
 			mac->ops.get_link_up_info(&adapter->hw,
@@ -3686,6 +3709,8 @@ static void e1000_watchdog_task(struct w
 
 			if (adapter->flags & FLAG_RX_NEEDS_RESTART)
 				schedule_work(&adapter->reset_task);
+			else
+				pm_schedule_suspend(netdev->dev.parent, 100);
 		}
 	}
 
@@ -4489,13 +4514,15 @@ out:
 	return retval;
 }
 
-static int __e1000_shutdown(struct pci_dev *pdev, bool *enable_wake)
+static int __e1000_shutdown(struct pci_dev *pdev, bool *enable_wake,
+			    bool runtime)
 {
 	struct net_device *netdev = pci_get_drvdata(pdev);
 	struct e1000_adapter *adapter = netdev_priv(netdev);
 	struct e1000_hw *hw = &adapter->hw;
 	u32 ctrl, ctrl_ext, rctl, status;
-	u32 wufc = adapter->wol;
+	/* Runtime suspend should only enable wakeup for link changes */
+	u32 wufc = runtime ? E1000_WUFC_LNKC : adapter->wol;
 	int retval = 0;
 
 	netif_device_detach(netdev);
@@ -4653,41 +4680,43 @@ static void e1000e_disable_l1aspm(struct
 }
 
 #ifdef CONFIG_PM
-static int e1000_suspend(struct pci_dev *pdev, pm_message_t state)
+static int e1000_idle(struct device *dev)
 {
+	/* Prevent the driver core from changing our state */
+	return -EBUSY;
+}
+
+static int e1000_suspend(struct device *dev)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
 	int retval;
 	bool wake;
 
-	retval = __e1000_shutdown(pdev, &wake);
+	retval = __e1000_shutdown(pdev, &wake, false);
 	if (!retval)
 		e1000_complete_shutdown(pdev, true, wake);
 
 	return retval;
 }
 
-static int e1000_resume(struct pci_dev *pdev)
+static int e1000_runtime_suspend(struct device *dev)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
+	bool wake;
+
+	return __e1000_shutdown(pdev, &wake, true);
+}
+
+static int e1000_resume(struct device *dev)
 {
+	struct pci_dev *pdev = to_pci_dev(dev);
 	struct net_device *netdev = pci_get_drvdata(pdev);
 	struct e1000_adapter *adapter = netdev_priv(netdev);
 	struct e1000_hw *hw = &adapter->hw;
 	u32 err;
 
-	pci_set_power_state(pdev, PCI_D0);
-	pci_restore_state(pdev);
 	e1000e_disable_l1aspm(pdev);
 
-	err = pci_enable_device_mem(pdev);
-	if (err) {
-		dev_err(&pdev->dev,
-			"Cannot enable PCI device from suspend\n");
-		return err;
-	}
-
-	pci_set_master(pdev);
-
-	pci_enable_wake(pdev, PCI_D3hot, 0);
-	pci_enable_wake(pdev, PCI_D3cold, 0);
-
 	e1000e_set_interrupt_capability(adapter);
 	if (netif_running(netdev)) {
 		err = e1000_request_irq(adapter);
@@ -4751,7 +4780,7 @@ static void e1000_shutdown(struct pci_de
 {
 	bool wake = false;
 
-	__e1000_shutdown(pdev, &wake);
+	__e1000_shutdown(pdev, &wake, false);
 
 	if (system_state == SYSTEM_POWER_OFF)
 		e1000_complete_shutdown(pdev, false, wake);
@@ -5274,6 +5303,9 @@ static void __devexit e1000_remove(struc
 	struct net_device *netdev = pci_get_drvdata(pdev);
 	struct e1000_adapter *adapter = netdev_priv(netdev);
 
+	if (pci_dev_run_wake(pdev))
+		pm_runtime_resume(&pdev->dev);
+
 	/*
 	 * flush_scheduled work may reschedule our watchdog task, so
 	 * explicitly disable watchdog tasks from being rescheduled
@@ -5393,6 +5425,18 @@ static struct pci_device_id e1000_pci_tb
 };
 MODULE_DEVICE_TABLE(pci, e1000_pci_tbl);
 
+static struct dev_pm_ops e1000_pm_ops = {
+	.suspend  = e1000_suspend,
+	.resume   = e1000_resume,
+	.freeze = e1000_suspend,
+	.thaw = e1000_resume,
+	.poweroff = e1000_suspend,
+	.restore = e1000_resume,
+	.runtime_suspend = e1000_runtime_suspend,
+	.runtime_resume = e1000_resume,
+	.runtime_idle = e1000_idle,
+};
+
 /* PCI Device API Driver */
 static struct pci_driver e1000_driver = {
 	.name     = e1000e_driver_name,
@@ -5400,9 +5444,7 @@ static struct pci_driver e1000_driver = 
 	.probe    = e1000_probe,
 	.remove   = __devexit_p(e1000_remove),
 #ifdef CONFIG_PM
-	/* Power Management Hooks */
-	.suspend  = e1000_suspend,
-	.resume   = e1000_resume,
+	.driver.pm = &e1000_pm_ops,
 #endif
 	.shutdown = e1000_shutdown,
 	.err_handler = &e1000_err_handler

^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH][RFC] e1000e: Add basic runtime PM support (rev. 2) (was: [PATCH 0/12] PCI run-time PM support (rev. 2))
  2010-01-01  1:29   ` Rafael J. Wysocki
  (?)
@ 2010-01-01 19:03   ` Rafael J. Wysocki
  2010-01-01 21:51     ` [PATCH][RFC] e1000e: Add basic runtime PM support (rev. 3) " Rafael J. Wysocki
  2010-01-01 21:51     ` Rafael J. Wysocki
  -1 siblings, 2 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2010-01-01 19:03 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Matthew Garrett, Len Brown, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu, Jeff Kirsher, e1000-devel

On Friday 01 January 2010, Rafael J. Wysocki wrote:
> On Sunday 27 December 2009, Rafael J. Wysocki wrote:
> > Hi,
> > 
> > The following (updated) series of patches provides preliminary run-time power
> > management support for PCI devices through ACPI and/or the native PCIe PME.
> > 
> > Some patches have been modified since the previous iteration, one patch has
> > been merged and there's one more.
> > 
> > I've tested this patchset with the native PCIe PME mechanism using the r8169
> > driver on the MSI Wind U-100 (see the last patch for details) and with the ACPI
> > mechanism using the e1000e driver on the Toshiba Portege R500 (the patch still
> > requires some work to be shown in public ;-)).
> 
> The e1000e patch is now in a better shape IMO, at least it worked for me in all
> conditions I tested it, so it is appended below.

This one was actually buggy, as it triggered a netdevice.h BUG_ON() during
resume from suspend to RAM with network cable attached.

Appended is a new version that doesn't have this problem and enables runtime PM
in _probe(), so the device can be put into the low power state if _open()
hasn't been called yet (or after _close()).

Rafael

---
From: Rafael J. Wysocki <rjw@sisk.pl>
Subject: PCI / PM / e1000e: Add basic runtime PM support (rev. 2)

Use the PCI run-time power management framework to add simplified
run-time PM support to the e1000e driver.  Namely, make the driver
suspend the device when the link is off and set it up for generating
wake-up event after the link has been detected again.

Based on a patch from Matthew Garrett.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
 drivers/net/e1000e/netdev.c |  130 +++++++++++++++++++++++++++++++++++---------
 1 file changed, 106 insertions(+), 24 deletions(-)

Index: linux-2.6/drivers/net/e1000e/netdev.c
===================================================================
--- linux-2.6.orig/drivers/net/e1000e/netdev.c
+++ linux-2.6/drivers/net/e1000e/netdev.c
@@ -44,6 +44,7 @@
 #include <linux/cpu.h>
 #include <linux/smp.h>
 #include <linux/pm_qos_params.h>
+#include <linux/pm_runtime.h>
 #include <linux/aer.h>
 
 #include "e1000.h"
@@ -3093,12 +3094,15 @@ static int e1000_open(struct net_device 
 {
 	struct e1000_adapter *adapter = netdev_priv(netdev);
 	struct e1000_hw *hw = &adapter->hw;
+	struct pci_dev *pdev = adapter->pdev;
 	int err;
 
 	/* disallow open during test */
 	if (test_bit(__E1000_TESTING, &adapter->state))
 		return -EBUSY;
 
+	pm_runtime_get_sync(&pdev->dev);
+
 	netif_carrier_off(netdev);
 
 	/* allocate transmit descriptors */
@@ -3162,6 +3166,8 @@ static int e1000_open(struct net_device 
 	/* fire a link status change interrupt to start the watchdog */
 	ew32(ICS, E1000_ICS_LSC);
 
+	pm_runtime_put_noidle(&pdev->dev);
+
 	return 0;
 
 err_req_irq:
@@ -3172,6 +3178,7 @@ err_setup_rx:
 	e1000e_free_tx_resources(adapter);
 err_setup_tx:
 	e1000e_reset(adapter);
+	pm_runtime_put_noidle(&pdev->dev);
 
 	return err;
 }
@@ -3190,9 +3197,15 @@ err_setup_tx:
 static int e1000_close(struct net_device *netdev)
 {
 	struct e1000_adapter *adapter = netdev_priv(netdev);
+	struct pci_dev *pdev = adapter->pdev;
 
 	WARN_ON(test_bit(__E1000_RESETTING, &adapter->state));
-	e1000e_down(adapter);
+
+	pm_runtime_get_sync(&pdev->dev);
+
+	if (!test_bit(__E1000_DOWN, &adapter->state))
+		e1000e_down(adapter);
+
 	e1000_power_down_phy(adapter);
 	e1000_free_irq(adapter);
 
@@ -3216,6 +3229,8 @@ static int e1000_close(struct net_device
 	if (adapter->flags & FLAG_HAS_AMT)
 		e1000_release_hw_control(adapter);
 
+	pm_runtime_put_noidle(&pdev->dev);
+
 	return 0;
 }
 /**
@@ -3571,6 +3586,10 @@ static void e1000_watchdog_task(struct w
 	if (link) {
 		if (!netif_carrier_ok(netdev)) {
 			bool txb2b = 1;
+
+			/* This is to cancel scheduled suspend requests. */
+			pm_runtime_resume(netdev->dev.parent);
+
 			/* update snapshot of PHY registers on LSC */
 			e1000_phy_read_status(adapter);
 			mac->ops.get_link_up_info(&adapter->hw,
@@ -3686,6 +3705,8 @@ static void e1000_watchdog_task(struct w
 
 			if (adapter->flags & FLAG_RX_NEEDS_RESTART)
 				schedule_work(&adapter->reset_task);
+			else
+				pm_schedule_suspend(netdev->dev.parent, 100);
 		}
 	}
 
@@ -4489,13 +4510,15 @@ out:
 	return retval;
 }
 
-static int __e1000_shutdown(struct pci_dev *pdev, bool *enable_wake)
+static int __e1000_shutdown(struct pci_dev *pdev, bool *enable_wake,
+			    bool runtime)
 {
 	struct net_device *netdev = pci_get_drvdata(pdev);
 	struct e1000_adapter *adapter = netdev_priv(netdev);
 	struct e1000_hw *hw = &adapter->hw;
 	u32 ctrl, ctrl_ext, rctl, status;
-	u32 wufc = adapter->wol;
+	/* Runtime suspend should only enable wakeup for link changes */
+	u32 wufc = runtime ? E1000_WUFC_LNKC : adapter->wol;
 	int retval = 0;
 
 	netif_device_detach(netdev);
@@ -4653,40 +4676,59 @@ static void e1000e_disable_l1aspm(struct
 }
 
 #ifdef CONFIG_PM
-static int e1000_suspend(struct pci_dev *pdev, pm_message_t state)
+static bool e1000e_pm_ready(struct e1000_adapter *adapter)
+{
+	return !!adapter->tx_ring->buffer_info;
+}
+
+static int e1000_idle(struct device *dev)
 {
+	struct pci_dev *pdev = to_pci_dev(dev);
+	struct net_device *netdev = pci_get_drvdata(pdev);
+	struct e1000_adapter *adapter = netdev_priv(netdev);
+
+	/*  If we're active, prevent the driver core from changing our state */
+	if (e1000e_pm_ready(adapter))
+		return -EBUSY;
+
+	pm_schedule_suspend(dev, MSEC_PER_SEC);
+	return 0;
+}
+
+static int e1000_suspend(struct device *dev)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
 	int retval;
 	bool wake;
 
-	retval = __e1000_shutdown(pdev, &wake);
+	retval = __e1000_shutdown(pdev, &wake, false);
 	if (!retval)
 		e1000_complete_shutdown(pdev, true, wake);
 
 	return retval;
 }
 
-static int e1000_resume(struct pci_dev *pdev)
+static int e1000_runtime_suspend(struct device *dev)
 {
+	struct pci_dev *pdev = to_pci_dev(dev);
 	struct net_device *netdev = pci_get_drvdata(pdev);
 	struct e1000_adapter *adapter = netdev_priv(netdev);
-	struct e1000_hw *hw = &adapter->hw;
-	u32 err;
+	bool wake;
 
-	pci_set_power_state(pdev, PCI_D0);
-	pci_restore_state(pdev);
-	e1000e_disable_l1aspm(pdev);
+	if (e1000e_pm_ready(adapter))
+		return __e1000_shutdown(pdev, &wake, true);
 
-	err = pci_enable_device_mem(pdev);
-	if (err) {
-		dev_err(&pdev->dev,
-			"Cannot enable PCI device from suspend\n");
-		return err;
-	}
+	return 0;
+}
 
-	pci_set_master(pdev);
+static int __e1000_resume(struct pci_dev *pdev)
+{
+	struct net_device *netdev = pci_get_drvdata(pdev);
+	struct e1000_adapter *adapter = netdev_priv(netdev);
+	struct e1000_hw *hw = &adapter->hw;
+	u32 err;
 
-	pci_enable_wake(pdev, PCI_D3hot, 0);
-	pci_enable_wake(pdev, PCI_D3cold, 0);
+	e1000e_disable_l1aspm(pdev);
 
 	e1000e_set_interrupt_capability(adapter);
 	if (netif_running(netdev)) {
@@ -4745,13 +4787,30 @@ static int e1000_resume(struct pci_dev *
 
 	return 0;
 }
+
+static int e1000_resume(struct device *dev)
+{
+	return __e1000_resume(to_pci_dev(dev));
+}
+
+static int e1000_runtime_resume(struct device *dev)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
+	struct net_device *netdev = pci_get_drvdata(pdev);
+	struct e1000_adapter *adapter = netdev_priv(netdev);
+
+	if (e1000e_pm_ready(adapter))
+		return __e1000_resume(pdev);
+
+	return 0;
+}
 #endif
 
 static void e1000_shutdown(struct pci_dev *pdev)
 {
 	bool wake = false;
 
-	__e1000_shutdown(pdev, &wake);
+	__e1000_shutdown(pdev, &wake, false);
 
 	if (system_state == SYSTEM_POWER_OFF)
 		e1000_complete_shutdown(pdev, false, wake);
@@ -5231,6 +5290,11 @@ static int __devinit e1000_probe(struct 
 
 	e1000_print_device_info(adapter);
 
+	if (pci_dev_run_wake(pdev)) {
+		pm_runtime_set_active(&pdev->dev);
+		pm_runtime_enable(&pdev->dev);
+	}
+
 	return 0;
 
 err_register:
@@ -5274,6 +5338,8 @@ static void __devexit e1000_remove(struc
 	struct net_device *netdev = pci_get_drvdata(pdev);
 	struct e1000_adapter *adapter = netdev_priv(netdev);
 
+	pm_runtime_get_sync(&pdev->dev);
+
 	/*
 	 * flush_scheduled work may reschedule our watchdog task, so
 	 * explicitly disable watchdog tasks from being rescheduled
@@ -5294,6 +5360,12 @@ static void __devexit e1000_remove(struc
 
 	unregister_netdev(netdev);
 
+	if (pci_dev_run_wake(pdev)) {
+		pm_runtime_disable(&pdev->dev);
+		pm_runtime_set_suspended(&pdev->dev);
+	}
+	pm_runtime_put_noidle(&pdev->dev);
+
 	/*
 	 * Release control of h/w to f/w.  If f/w is AMT enabled, this
 	 * would have already happened in close and is redundant.
@@ -5393,6 +5465,18 @@ static struct pci_device_id e1000_pci_tb
 };
 MODULE_DEVICE_TABLE(pci, e1000_pci_tbl);
 
+static const struct dev_pm_ops e1000_pm_ops = {
+	.suspend  = e1000_suspend,
+	.resume   = e1000_resume,
+	.freeze = e1000_suspend,
+	.thaw = e1000_resume,
+	.poweroff = e1000_suspend,
+	.restore = e1000_resume,
+	.runtime_suspend = e1000_runtime_suspend,
+	.runtime_resume = e1000_runtime_resume,
+	.runtime_idle = e1000_idle,
+};
+
 /* PCI Device API Driver */
 static struct pci_driver e1000_driver = {
 	.name     = e1000e_driver_name,
@@ -5400,9 +5484,7 @@ static struct pci_driver e1000_driver = 
 	.probe    = e1000_probe,
 	.remove   = __devexit_p(e1000_remove),
 #ifdef CONFIG_PM
-	/* Power Management Hooks */
-	.suspend  = e1000_suspend,
-	.resume   = e1000_resume,
+	.driver.pm = &e1000_pm_ops,
 #endif
 	.shutdown = e1000_shutdown,
 	.err_handler = &e1000_err_handler

^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH][RFC] e1000e: Add basic runtime PM support (rev. 2) (was: [PATCH 0/12] PCI run-time PM support (rev. 2))
  2010-01-01  1:29   ` Rafael J. Wysocki
  (?)
  (?)
@ 2010-01-01 19:03   ` Rafael J. Wysocki
  -1 siblings, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2010-01-01 19:03 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: e1000-devel, Linux PCI, LKML, ACPI Devel Maling List,
	Jeff Kirsher, Francois Romieu, pm list, Matthew Garrett

On Friday 01 January 2010, Rafael J. Wysocki wrote:
> On Sunday 27 December 2009, Rafael J. Wysocki wrote:
> > Hi,
> > 
> > The following (updated) series of patches provides preliminary run-time power
> > management support for PCI devices through ACPI and/or the native PCIe PME.
> > 
> > Some patches have been modified since the previous iteration, one patch has
> > been merged and there's one more.
> > 
> > I've tested this patchset with the native PCIe PME mechanism using the r8169
> > driver on the MSI Wind U-100 (see the last patch for details) and with the ACPI
> > mechanism using the e1000e driver on the Toshiba Portege R500 (the patch still
> > requires some work to be shown in public ;-)).
> 
> The e1000e patch is now in a better shape IMO, at least it worked for me in all
> conditions I tested it, so it is appended below.

This one was actually buggy, as it triggered a netdevice.h BUG_ON() during
resume from suspend to RAM with network cable attached.

Appended is a new version that doesn't have this problem and enables runtime PM
in _probe(), so the device can be put into the low power state if _open()
hasn't been called yet (or after _close()).

Rafael

---
From: Rafael J. Wysocki <rjw@sisk.pl>
Subject: PCI / PM / e1000e: Add basic runtime PM support (rev. 2)

Use the PCI run-time power management framework to add simplified
run-time PM support to the e1000e driver.  Namely, make the driver
suspend the device when the link is off and set it up for generating
wake-up event after the link has been detected again.

Based on a patch from Matthew Garrett.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
 drivers/net/e1000e/netdev.c |  130 +++++++++++++++++++++++++++++++++++---------
 1 file changed, 106 insertions(+), 24 deletions(-)

Index: linux-2.6/drivers/net/e1000e/netdev.c
===================================================================
--- linux-2.6.orig/drivers/net/e1000e/netdev.c
+++ linux-2.6/drivers/net/e1000e/netdev.c
@@ -44,6 +44,7 @@
 #include <linux/cpu.h>
 #include <linux/smp.h>
 #include <linux/pm_qos_params.h>
+#include <linux/pm_runtime.h>
 #include <linux/aer.h>
 
 #include "e1000.h"
@@ -3093,12 +3094,15 @@ static int e1000_open(struct net_device 
 {
 	struct e1000_adapter *adapter = netdev_priv(netdev);
 	struct e1000_hw *hw = &adapter->hw;
+	struct pci_dev *pdev = adapter->pdev;
 	int err;
 
 	/* disallow open during test */
 	if (test_bit(__E1000_TESTING, &adapter->state))
 		return -EBUSY;
 
+	pm_runtime_get_sync(&pdev->dev);
+
 	netif_carrier_off(netdev);
 
 	/* allocate transmit descriptors */
@@ -3162,6 +3166,8 @@ static int e1000_open(struct net_device 
 	/* fire a link status change interrupt to start the watchdog */
 	ew32(ICS, E1000_ICS_LSC);
 
+	pm_runtime_put_noidle(&pdev->dev);
+
 	return 0;
 
 err_req_irq:
@@ -3172,6 +3178,7 @@ err_setup_rx:
 	e1000e_free_tx_resources(adapter);
 err_setup_tx:
 	e1000e_reset(adapter);
+	pm_runtime_put_noidle(&pdev->dev);
 
 	return err;
 }
@@ -3190,9 +3197,15 @@ err_setup_tx:
 static int e1000_close(struct net_device *netdev)
 {
 	struct e1000_adapter *adapter = netdev_priv(netdev);
+	struct pci_dev *pdev = adapter->pdev;
 
 	WARN_ON(test_bit(__E1000_RESETTING, &adapter->state));
-	e1000e_down(adapter);
+
+	pm_runtime_get_sync(&pdev->dev);
+
+	if (!test_bit(__E1000_DOWN, &adapter->state))
+		e1000e_down(adapter);
+
 	e1000_power_down_phy(adapter);
 	e1000_free_irq(adapter);
 
@@ -3216,6 +3229,8 @@ static int e1000_close(struct net_device
 	if (adapter->flags & FLAG_HAS_AMT)
 		e1000_release_hw_control(adapter);
 
+	pm_runtime_put_noidle(&pdev->dev);
+
 	return 0;
 }
 /**
@@ -3571,6 +3586,10 @@ static void e1000_watchdog_task(struct w
 	if (link) {
 		if (!netif_carrier_ok(netdev)) {
 			bool txb2b = 1;
+
+			/* This is to cancel scheduled suspend requests. */
+			pm_runtime_resume(netdev->dev.parent);
+
 			/* update snapshot of PHY registers on LSC */
 			e1000_phy_read_status(adapter);
 			mac->ops.get_link_up_info(&adapter->hw,
@@ -3686,6 +3705,8 @@ static void e1000_watchdog_task(struct w
 
 			if (adapter->flags & FLAG_RX_NEEDS_RESTART)
 				schedule_work(&adapter->reset_task);
+			else
+				pm_schedule_suspend(netdev->dev.parent, 100);
 		}
 	}
 
@@ -4489,13 +4510,15 @@ out:
 	return retval;
 }
 
-static int __e1000_shutdown(struct pci_dev *pdev, bool *enable_wake)
+static int __e1000_shutdown(struct pci_dev *pdev, bool *enable_wake,
+			    bool runtime)
 {
 	struct net_device *netdev = pci_get_drvdata(pdev);
 	struct e1000_adapter *adapter = netdev_priv(netdev);
 	struct e1000_hw *hw = &adapter->hw;
 	u32 ctrl, ctrl_ext, rctl, status;
-	u32 wufc = adapter->wol;
+	/* Runtime suspend should only enable wakeup for link changes */
+	u32 wufc = runtime ? E1000_WUFC_LNKC : adapter->wol;
 	int retval = 0;
 
 	netif_device_detach(netdev);
@@ -4653,40 +4676,59 @@ static void e1000e_disable_l1aspm(struct
 }
 
 #ifdef CONFIG_PM
-static int e1000_suspend(struct pci_dev *pdev, pm_message_t state)
+static bool e1000e_pm_ready(struct e1000_adapter *adapter)
+{
+	return !!adapter->tx_ring->buffer_info;
+}
+
+static int e1000_idle(struct device *dev)
 {
+	struct pci_dev *pdev = to_pci_dev(dev);
+	struct net_device *netdev = pci_get_drvdata(pdev);
+	struct e1000_adapter *adapter = netdev_priv(netdev);
+
+	/*  If we're active, prevent the driver core from changing our state */
+	if (e1000e_pm_ready(adapter))
+		return -EBUSY;
+
+	pm_schedule_suspend(dev, MSEC_PER_SEC);
+	return 0;
+}
+
+static int e1000_suspend(struct device *dev)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
 	int retval;
 	bool wake;
 
-	retval = __e1000_shutdown(pdev, &wake);
+	retval = __e1000_shutdown(pdev, &wake, false);
 	if (!retval)
 		e1000_complete_shutdown(pdev, true, wake);
 
 	return retval;
 }
 
-static int e1000_resume(struct pci_dev *pdev)
+static int e1000_runtime_suspend(struct device *dev)
 {
+	struct pci_dev *pdev = to_pci_dev(dev);
 	struct net_device *netdev = pci_get_drvdata(pdev);
 	struct e1000_adapter *adapter = netdev_priv(netdev);
-	struct e1000_hw *hw = &adapter->hw;
-	u32 err;
+	bool wake;
 
-	pci_set_power_state(pdev, PCI_D0);
-	pci_restore_state(pdev);
-	e1000e_disable_l1aspm(pdev);
+	if (e1000e_pm_ready(adapter))
+		return __e1000_shutdown(pdev, &wake, true);
 
-	err = pci_enable_device_mem(pdev);
-	if (err) {
-		dev_err(&pdev->dev,
-			"Cannot enable PCI device from suspend\n");
-		return err;
-	}
+	return 0;
+}
 
-	pci_set_master(pdev);
+static int __e1000_resume(struct pci_dev *pdev)
+{
+	struct net_device *netdev = pci_get_drvdata(pdev);
+	struct e1000_adapter *adapter = netdev_priv(netdev);
+	struct e1000_hw *hw = &adapter->hw;
+	u32 err;
 
-	pci_enable_wake(pdev, PCI_D3hot, 0);
-	pci_enable_wake(pdev, PCI_D3cold, 0);
+	e1000e_disable_l1aspm(pdev);
 
 	e1000e_set_interrupt_capability(adapter);
 	if (netif_running(netdev)) {
@@ -4745,13 +4787,30 @@ static int e1000_resume(struct pci_dev *
 
 	return 0;
 }
+
+static int e1000_resume(struct device *dev)
+{
+	return __e1000_resume(to_pci_dev(dev));
+}
+
+static int e1000_runtime_resume(struct device *dev)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
+	struct net_device *netdev = pci_get_drvdata(pdev);
+	struct e1000_adapter *adapter = netdev_priv(netdev);
+
+	if (e1000e_pm_ready(adapter))
+		return __e1000_resume(pdev);
+
+	return 0;
+}
 #endif
 
 static void e1000_shutdown(struct pci_dev *pdev)
 {
 	bool wake = false;
 
-	__e1000_shutdown(pdev, &wake);
+	__e1000_shutdown(pdev, &wake, false);
 
 	if (system_state == SYSTEM_POWER_OFF)
 		e1000_complete_shutdown(pdev, false, wake);
@@ -5231,6 +5290,11 @@ static int __devinit e1000_probe(struct 
 
 	e1000_print_device_info(adapter);
 
+	if (pci_dev_run_wake(pdev)) {
+		pm_runtime_set_active(&pdev->dev);
+		pm_runtime_enable(&pdev->dev);
+	}
+
 	return 0;
 
 err_register:
@@ -5274,6 +5338,8 @@ static void __devexit e1000_remove(struc
 	struct net_device *netdev = pci_get_drvdata(pdev);
 	struct e1000_adapter *adapter = netdev_priv(netdev);
 
+	pm_runtime_get_sync(&pdev->dev);
+
 	/*
 	 * flush_scheduled work may reschedule our watchdog task, so
 	 * explicitly disable watchdog tasks from being rescheduled
@@ -5294,6 +5360,12 @@ static void __devexit e1000_remove(struc
 
 	unregister_netdev(netdev);
 
+	if (pci_dev_run_wake(pdev)) {
+		pm_runtime_disable(&pdev->dev);
+		pm_runtime_set_suspended(&pdev->dev);
+	}
+	pm_runtime_put_noidle(&pdev->dev);
+
 	/*
 	 * Release control of h/w to f/w.  If f/w is AMT enabled, this
 	 * would have already happened in close and is redundant.
@@ -5393,6 +5465,18 @@ static struct pci_device_id e1000_pci_tb
 };
 MODULE_DEVICE_TABLE(pci, e1000_pci_tbl);
 
+static const struct dev_pm_ops e1000_pm_ops = {
+	.suspend  = e1000_suspend,
+	.resume   = e1000_resume,
+	.freeze = e1000_suspend,
+	.thaw = e1000_resume,
+	.poweroff = e1000_suspend,
+	.restore = e1000_resume,
+	.runtime_suspend = e1000_runtime_suspend,
+	.runtime_resume = e1000_runtime_resume,
+	.runtime_idle = e1000_idle,
+};
+
 /* PCI Device API Driver */
 static struct pci_driver e1000_driver = {
 	.name     = e1000e_driver_name,
@@ -5400,9 +5484,7 @@ static struct pci_driver e1000_driver = 
 	.probe    = e1000_probe,
 	.remove   = __devexit_p(e1000_remove),
 #ifdef CONFIG_PM
-	/* Power Management Hooks */
-	.suspend  = e1000_suspend,
-	.resume   = e1000_resume,
+	.driver.pm = &e1000_pm_ops,
 #endif
 	.shutdown = e1000_shutdown,
 	.err_handler = &e1000_err_handler

^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH 12/12] PM / r8169: Add simplified run-time PM support (rev. 2)
  2009-12-27 20:11 ` Rafael J. Wysocki
@ 2010-01-01 19:06   ` Rafael J. Wysocki
  2010-01-01 19:06   ` Rafael J. Wysocki
  1 sibling, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2010-01-01 19:06 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Matthew Garrett, Len Brown, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu

On Sunday 27 December 2009, Rafael J. Wysocki wrote:
> From: Rafael J. Wysocki <rjw@sisk.pl>
> 
> Use the PCI run-time power management framework to add simplified
> run-time PM support to the r8169 driver.  Namely, make the driver
> suspend the device when the link is off and set it up for generating
> wake-up event after the link has been detected again.
> 
> Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>

Appended is an improved (in my opinion) version of this patch that enables
the runtime PM in _init(), so that the device can be put into the low power
state before _open() or after _close().

Rafael

---
From: Rafael J. Wysocki <rjw@sisk.pl>
Subject: PCI / PM / r8169: Add simplified run-time PM support (rev. 2)

Use the PCI run-time power management framework to add simplified
run-time PM support to the r8169 driver.  Namely, make the driver
suspend the device when the link is off and set it up for generating
wake-up event after the link has been detected again.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
 drivers/net/r8169.c |  151 ++++++++++++++++++++++++++++++++++++++++++----------
 1 file changed, 124 insertions(+), 27 deletions(-)

Index: linux-2.6/drivers/net/r8169.c
===================================================================
--- linux-2.6.orig/drivers/net/r8169.c
+++ linux-2.6/drivers/net/r8169.c
@@ -23,6 +23,7 @@
 #include <linux/tcp.h>
 #include <linux/init.h>
 #include <linux/dma-mapping.h>
+#include <linux/pm_runtime.h>
 
 #include <asm/system.h>
 #include <asm/io.h>
@@ -504,6 +505,7 @@ struct rtl8169_private {
 
 	struct mii_if_info mii;
 	struct rtl8169_counters counters;
+	u32 saved_wolopts;
 };
 
 MODULE_AUTHOR("Realtek and the Linux r8169 crew <netdev@vger.kernel.org>");
@@ -743,6 +745,8 @@ static void rtl8169_check_link_status(st
 
 	spin_lock_irqsave(&tp->lock, flags);
 	if (tp->link_ok(ioaddr)) {
+		/* This is to cancel a scheduled suspend if there's one. */
+		pm_request_resume(&tp->pci_dev->dev);
 		netif_carrier_on(dev);
 		if (netif_msg_ifup(tp))
 			printk(KERN_INFO PFX "%s: link up\n", dev->name);
@@ -750,48 +754,54 @@ static void rtl8169_check_link_status(st
 		if (netif_msg_ifdown(tp))
 			printk(KERN_INFO PFX "%s: link down\n", dev->name);
 		netif_carrier_off(dev);
+		pm_schedule_suspend(&tp->pci_dev->dev, 100);
 	}
 	spin_unlock_irqrestore(&tp->lock, flags);
 }
 
-static void rtl8169_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
+#define WAKE_ANY (WAKE_PHY | WAKE_MAGIC | WAKE_UCAST | WAKE_BCAST | WAKE_MCAST)
+
+static u32 __rtl8169_get_wol(struct rtl8169_private *tp)
 {
-	struct rtl8169_private *tp = netdev_priv(dev);
 	void __iomem *ioaddr = tp->mmio_addr;
 	u8 options;
-
-	wol->wolopts = 0;
-
-#define WAKE_ANY (WAKE_PHY | WAKE_MAGIC | WAKE_UCAST | WAKE_BCAST | WAKE_MCAST)
-	wol->supported = WAKE_ANY;
-
-	spin_lock_irq(&tp->lock);
+	u32 wolopts = 0;
 
 	options = RTL_R8(Config1);
 	if (!(options & PMEnable))
-		goto out_unlock;
+		return 0;
 
 	options = RTL_R8(Config3);
 	if (options & LinkUp)
-		wol->wolopts |= WAKE_PHY;
+		wolopts |= WAKE_PHY;
 	if (options & MagicPacket)
-		wol->wolopts |= WAKE_MAGIC;
+		wolopts |= WAKE_MAGIC;
 
 	options = RTL_R8(Config5);
 	if (options & UWF)
-		wol->wolopts |= WAKE_UCAST;
+		wolopts |= WAKE_UCAST;
 	if (options & BWF)
-		wol->wolopts |= WAKE_BCAST;
+		wolopts |= WAKE_BCAST;
 	if (options & MWF)
-		wol->wolopts |= WAKE_MCAST;
+		wolopts |= WAKE_MCAST;
 
-out_unlock:
-	spin_unlock_irq(&tp->lock);
+	return wolopts;
 }
 
-static int rtl8169_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
+static void rtl8169_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
 {
 	struct rtl8169_private *tp = netdev_priv(dev);
+
+	spin_lock_irq(&tp->lock);
+
+	wol->supported = WAKE_ANY;
+	wol->wolopts = __rtl8169_get_wol(tp);
+
+	spin_unlock_irq(&tp->lock);
+}
+
+static void __rtl8169_set_wol(struct rtl8169_private *tp, u32 wolopts)
+{
 	void __iomem *ioaddr = tp->mmio_addr;
 	unsigned int i;
 	static const struct {
@@ -808,23 +818,29 @@ static int rtl8169_set_wol(struct net_de
 		{ WAKE_ANY,   Config5, LanWake }
 	};
 
-	spin_lock_irq(&tp->lock);
-
 	RTL_W8(Cfg9346, Cfg9346_Unlock);
 
 	for (i = 0; i < ARRAY_SIZE(cfg); i++) {
 		u8 options = RTL_R8(cfg[i].reg) & ~cfg[i].mask;
-		if (wol->wolopts & cfg[i].opt)
+		if (wolopts & cfg[i].opt)
 			options |= cfg[i].mask;
 		RTL_W8(cfg[i].reg, options);
 	}
 
 	RTL_W8(Cfg9346, Cfg9346_Lock);
+}
+
+static int rtl8169_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
+{
+	struct rtl8169_private *tp = netdev_priv(dev);
+
+	spin_lock_irq(&tp->lock);
 
 	if (wol->wolopts)
 		tp->features |= RTL_FEATURE_WOL;
 	else
 		tp->features &= ~RTL_FEATURE_WOL;
+	__rtl8169_set_wol(tp, wol->wolopts);
 	device_set_wakeup_enable(&tp->pci_dev->dev, wol->wolopts);
 
 	spin_unlock_irq(&tp->lock);
@@ -3210,6 +3226,11 @@ rtl8169_init_one(struct pci_dev *pdev, c
 
 	device_set_wakeup_enable(&pdev->dev, tp->features & RTL_FEATURE_WOL);
 
+	if (pci_dev_run_wake(pdev)) {
+		pm_runtime_set_active(&pdev->dev);
+		pm_runtime_enable(&pdev->dev);
+	}
+
 out:
 	return rc;
 
@@ -3232,10 +3253,18 @@ static void __devexit rtl8169_remove_one
 	struct net_device *dev = pci_get_drvdata(pdev);
 	struct rtl8169_private *tp = netdev_priv(dev);
 
+	pm_runtime_get_sync(&pdev->dev);
+
 	flush_scheduled_work();
 
 	unregister_netdev(dev);
 
+	if (pci_dev_run_wake(pdev)) {
+		pm_runtime_disable(&pdev->dev);
+		pm_runtime_set_suspended(&pdev->dev);
+	}
+	pm_runtime_put_noidle(&pdev->dev);
+
 	/* restore original MAC address */
 	rtl_rar_set(tp, dev->perm_addr);
 
@@ -3258,6 +3287,7 @@ static int rtl8169_open(struct net_devic
 	struct pci_dev *pdev = tp->pci_dev;
 	int retval = -ENOMEM;
 
+	pm_runtime_get_sync(&pdev->dev);
 
 	rtl8169_set_rxbufsize(tp, dev);
 
@@ -3268,7 +3298,7 @@ static int rtl8169_open(struct net_devic
 	tp->TxDescArray = pci_alloc_consistent(pdev, R8169_TX_RING_BYTES,
 					       &tp->TxPhyAddr);
 	if (!tp->TxDescArray)
-		goto out;
+		goto err_pm_runtime_put;
 
 	tp->RxDescArray = pci_alloc_consistent(pdev, R8169_RX_RING_BYTES,
 					       &tp->RxPhyAddr);
@@ -3295,6 +3325,9 @@ static int rtl8169_open(struct net_devic
 
 	rtl8169_request_timer(dev);
 
+	tp->saved_wolopts = 0;
+	pm_runtime_put_noidle(&pdev->dev);
+
 	rtl8169_check_link_status(dev, tp, tp->mmio_addr);
 out:
 	return retval;
@@ -3304,9 +3337,13 @@ err_release_ring_2:
 err_free_rx_1:
 	pci_free_consistent(pdev, R8169_RX_RING_BYTES, tp->RxDescArray,
 			    tp->RxPhyAddr);
+	tp->RxDescArray = NULL;
 err_free_tx_0:
 	pci_free_consistent(pdev, R8169_TX_RING_BYTES, tp->TxDescArray,
 			    tp->TxPhyAddr);
+	tp->TxDescArray = NULL;
+err_pm_runtime_put:
+	pm_runtime_put_noidle(&pdev->dev);
 	goto out;
 }
 
@@ -4727,6 +4764,8 @@ static int rtl8169_close(struct net_devi
 	struct rtl8169_private *tp = netdev_priv(dev);
 	struct pci_dev *pdev = tp->pci_dev;
 
+	pm_runtime_get_sync(&pdev->dev);
+
 	/* update counters before going down */
 	rtl8169_update_counters(dev);
 
@@ -4741,6 +4780,8 @@ static int rtl8169_close(struct net_devi
 	tp->TxDescArray = NULL;
 	tp->RxDescArray = NULL;
 
+	pm_runtime_put_noidle(&pdev->dev);
+
 	return 0;
 }
 
@@ -4844,18 +4885,71 @@ static int rtl8169_suspend(struct device
 	return 0;
 }
 
+static void __rtl8169_resume(struct net_device *dev)
+{
+	netif_device_attach(dev);
+	rtl8169_schedule_work(dev, rtl8169_reset_task);
+}
+
 static int rtl8169_resume(struct device *device)
 {
 	struct pci_dev *pdev = to_pci_dev(device);
 	struct net_device *dev = pci_get_drvdata(pdev);
 
-	if (!netif_running(dev))
-		goto out;
+	if (netif_running(dev))
+		__rtl8169_resume(dev);
 
-	netif_device_attach(dev);
+	return 0;
+}
 
-	rtl8169_schedule_work(dev, rtl8169_reset_task);
-out:
+static int rtl8169_runtime_suspend(struct device *device)
+{
+	struct pci_dev *pdev = to_pci_dev(device);
+	struct net_device *dev = pci_get_drvdata(pdev);
+	struct rtl8169_private *tp = netdev_priv(dev);
+
+	if (!tp->TxDescArray)
+		return 0;
+
+	spin_lock_irq(&tp->lock);
+	tp->saved_wolopts = __rtl8169_get_wol(tp);
+	__rtl8169_set_wol(tp, WAKE_ANY);
+	spin_unlock_irq(&tp->lock);
+
+	rtl8169_net_suspend(dev);
+
+	return 0;
+}
+
+static int rtl8169_runtime_resume(struct device *device)
+{
+	struct pci_dev *pdev = to_pci_dev(device);
+	struct net_device *dev = pci_get_drvdata(pdev);
+	struct rtl8169_private *tp = netdev_priv(dev);
+
+	if (!tp->TxDescArray)
+		return 0;
+
+	spin_lock_irq(&tp->lock);
+	__rtl8169_set_wol(tp, tp->saved_wolopts);
+	tp->saved_wolopts = 0;
+	spin_unlock_irq(&tp->lock);
+
+	__rtl8169_resume(dev);
+
+	return 0;
+}
+
+static int rtl8169_runtime_idle(struct device *device)
+{
+	struct pci_dev *pdev = to_pci_dev(device);
+	struct net_device *dev = pci_get_drvdata(pdev);
+	struct rtl8169_private *tp = netdev_priv(dev);
+
+	if (tp->TxDescArray)
+		return -EBUSY;
+
+	pm_schedule_suspend(device, MSEC_PER_SEC);
 	return 0;
 }
 
@@ -4866,6 +4960,9 @@ static const struct dev_pm_ops rtl8169_p
 	.thaw = rtl8169_resume,
 	.poweroff = rtl8169_suspend,
 	.restore = rtl8169_resume,
+	.runtime_suspend = rtl8169_runtime_suspend,
+	.runtime_resume = rtl8169_runtime_resume,
+	.runtime_idle = rtl8169_runtime_idle,
 };
 
 #define RTL8169_PM_OPS	(&rtl8169_pm_ops)

^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH 12/12] PM / r8169: Add simplified run-time PM support (rev. 2)
  2009-12-27 20:11 ` Rafael J. Wysocki
  2010-01-01 19:06   ` [PATCH 12/12] PM / r8169: Add simplified run-time PM support (rev. 2) Rafael J. Wysocki
@ 2010-01-01 19:06   ` Rafael J. Wysocki
  1 sibling, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2010-01-01 19:06 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Linux PCI, LKML, ACPI Devel Maling List, Francois Romieu,
	pm list, Matthew Garrett

On Sunday 27 December 2009, Rafael J. Wysocki wrote:
> From: Rafael J. Wysocki <rjw@sisk.pl>
> 
> Use the PCI run-time power management framework to add simplified
> run-time PM support to the r8169 driver.  Namely, make the driver
> suspend the device when the link is off and set it up for generating
> wake-up event after the link has been detected again.
> 
> Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>

Appended is an improved (in my opinion) version of this patch that enables
the runtime PM in _init(), so that the device can be put into the low power
state before _open() or after _close().

Rafael

---
From: Rafael J. Wysocki <rjw@sisk.pl>
Subject: PCI / PM / r8169: Add simplified run-time PM support (rev. 2)

Use the PCI run-time power management framework to add simplified
run-time PM support to the r8169 driver.  Namely, make the driver
suspend the device when the link is off and set it up for generating
wake-up event after the link has been detected again.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
 drivers/net/r8169.c |  151 ++++++++++++++++++++++++++++++++++++++++++----------
 1 file changed, 124 insertions(+), 27 deletions(-)

Index: linux-2.6/drivers/net/r8169.c
===================================================================
--- linux-2.6.orig/drivers/net/r8169.c
+++ linux-2.6/drivers/net/r8169.c
@@ -23,6 +23,7 @@
 #include <linux/tcp.h>
 #include <linux/init.h>
 #include <linux/dma-mapping.h>
+#include <linux/pm_runtime.h>
 
 #include <asm/system.h>
 #include <asm/io.h>
@@ -504,6 +505,7 @@ struct rtl8169_private {
 
 	struct mii_if_info mii;
 	struct rtl8169_counters counters;
+	u32 saved_wolopts;
 };
 
 MODULE_AUTHOR("Realtek and the Linux r8169 crew <netdev@vger.kernel.org>");
@@ -743,6 +745,8 @@ static void rtl8169_check_link_status(st
 
 	spin_lock_irqsave(&tp->lock, flags);
 	if (tp->link_ok(ioaddr)) {
+		/* This is to cancel a scheduled suspend if there's one. */
+		pm_request_resume(&tp->pci_dev->dev);
 		netif_carrier_on(dev);
 		if (netif_msg_ifup(tp))
 			printk(KERN_INFO PFX "%s: link up\n", dev->name);
@@ -750,48 +754,54 @@ static void rtl8169_check_link_status(st
 		if (netif_msg_ifdown(tp))
 			printk(KERN_INFO PFX "%s: link down\n", dev->name);
 		netif_carrier_off(dev);
+		pm_schedule_suspend(&tp->pci_dev->dev, 100);
 	}
 	spin_unlock_irqrestore(&tp->lock, flags);
 }
 
-static void rtl8169_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
+#define WAKE_ANY (WAKE_PHY | WAKE_MAGIC | WAKE_UCAST | WAKE_BCAST | WAKE_MCAST)
+
+static u32 __rtl8169_get_wol(struct rtl8169_private *tp)
 {
-	struct rtl8169_private *tp = netdev_priv(dev);
 	void __iomem *ioaddr = tp->mmio_addr;
 	u8 options;
-
-	wol->wolopts = 0;
-
-#define WAKE_ANY (WAKE_PHY | WAKE_MAGIC | WAKE_UCAST | WAKE_BCAST | WAKE_MCAST)
-	wol->supported = WAKE_ANY;
-
-	spin_lock_irq(&tp->lock);
+	u32 wolopts = 0;
 
 	options = RTL_R8(Config1);
 	if (!(options & PMEnable))
-		goto out_unlock;
+		return 0;
 
 	options = RTL_R8(Config3);
 	if (options & LinkUp)
-		wol->wolopts |= WAKE_PHY;
+		wolopts |= WAKE_PHY;
 	if (options & MagicPacket)
-		wol->wolopts |= WAKE_MAGIC;
+		wolopts |= WAKE_MAGIC;
 
 	options = RTL_R8(Config5);
 	if (options & UWF)
-		wol->wolopts |= WAKE_UCAST;
+		wolopts |= WAKE_UCAST;
 	if (options & BWF)
-		wol->wolopts |= WAKE_BCAST;
+		wolopts |= WAKE_BCAST;
 	if (options & MWF)
-		wol->wolopts |= WAKE_MCAST;
+		wolopts |= WAKE_MCAST;
 
-out_unlock:
-	spin_unlock_irq(&tp->lock);
+	return wolopts;
 }
 
-static int rtl8169_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
+static void rtl8169_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
 {
 	struct rtl8169_private *tp = netdev_priv(dev);
+
+	spin_lock_irq(&tp->lock);
+
+	wol->supported = WAKE_ANY;
+	wol->wolopts = __rtl8169_get_wol(tp);
+
+	spin_unlock_irq(&tp->lock);
+}
+
+static void __rtl8169_set_wol(struct rtl8169_private *tp, u32 wolopts)
+{
 	void __iomem *ioaddr = tp->mmio_addr;
 	unsigned int i;
 	static const struct {
@@ -808,23 +818,29 @@ static int rtl8169_set_wol(struct net_de
 		{ WAKE_ANY,   Config5, LanWake }
 	};
 
-	spin_lock_irq(&tp->lock);
-
 	RTL_W8(Cfg9346, Cfg9346_Unlock);
 
 	for (i = 0; i < ARRAY_SIZE(cfg); i++) {
 		u8 options = RTL_R8(cfg[i].reg) & ~cfg[i].mask;
-		if (wol->wolopts & cfg[i].opt)
+		if (wolopts & cfg[i].opt)
 			options |= cfg[i].mask;
 		RTL_W8(cfg[i].reg, options);
 	}
 
 	RTL_W8(Cfg9346, Cfg9346_Lock);
+}
+
+static int rtl8169_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
+{
+	struct rtl8169_private *tp = netdev_priv(dev);
+
+	spin_lock_irq(&tp->lock);
 
 	if (wol->wolopts)
 		tp->features |= RTL_FEATURE_WOL;
 	else
 		tp->features &= ~RTL_FEATURE_WOL;
+	__rtl8169_set_wol(tp, wol->wolopts);
 	device_set_wakeup_enable(&tp->pci_dev->dev, wol->wolopts);
 
 	spin_unlock_irq(&tp->lock);
@@ -3210,6 +3226,11 @@ rtl8169_init_one(struct pci_dev *pdev, c
 
 	device_set_wakeup_enable(&pdev->dev, tp->features & RTL_FEATURE_WOL);
 
+	if (pci_dev_run_wake(pdev)) {
+		pm_runtime_set_active(&pdev->dev);
+		pm_runtime_enable(&pdev->dev);
+	}
+
 out:
 	return rc;
 
@@ -3232,10 +3253,18 @@ static void __devexit rtl8169_remove_one
 	struct net_device *dev = pci_get_drvdata(pdev);
 	struct rtl8169_private *tp = netdev_priv(dev);
 
+	pm_runtime_get_sync(&pdev->dev);
+
 	flush_scheduled_work();
 
 	unregister_netdev(dev);
 
+	if (pci_dev_run_wake(pdev)) {
+		pm_runtime_disable(&pdev->dev);
+		pm_runtime_set_suspended(&pdev->dev);
+	}
+	pm_runtime_put_noidle(&pdev->dev);
+
 	/* restore original MAC address */
 	rtl_rar_set(tp, dev->perm_addr);
 
@@ -3258,6 +3287,7 @@ static int rtl8169_open(struct net_devic
 	struct pci_dev *pdev = tp->pci_dev;
 	int retval = -ENOMEM;
 
+	pm_runtime_get_sync(&pdev->dev);
 
 	rtl8169_set_rxbufsize(tp, dev);
 
@@ -3268,7 +3298,7 @@ static int rtl8169_open(struct net_devic
 	tp->TxDescArray = pci_alloc_consistent(pdev, R8169_TX_RING_BYTES,
 					       &tp->TxPhyAddr);
 	if (!tp->TxDescArray)
-		goto out;
+		goto err_pm_runtime_put;
 
 	tp->RxDescArray = pci_alloc_consistent(pdev, R8169_RX_RING_BYTES,
 					       &tp->RxPhyAddr);
@@ -3295,6 +3325,9 @@ static int rtl8169_open(struct net_devic
 
 	rtl8169_request_timer(dev);
 
+	tp->saved_wolopts = 0;
+	pm_runtime_put_noidle(&pdev->dev);
+
 	rtl8169_check_link_status(dev, tp, tp->mmio_addr);
 out:
 	return retval;
@@ -3304,9 +3337,13 @@ err_release_ring_2:
 err_free_rx_1:
 	pci_free_consistent(pdev, R8169_RX_RING_BYTES, tp->RxDescArray,
 			    tp->RxPhyAddr);
+	tp->RxDescArray = NULL;
 err_free_tx_0:
 	pci_free_consistent(pdev, R8169_TX_RING_BYTES, tp->TxDescArray,
 			    tp->TxPhyAddr);
+	tp->TxDescArray = NULL;
+err_pm_runtime_put:
+	pm_runtime_put_noidle(&pdev->dev);
 	goto out;
 }
 
@@ -4727,6 +4764,8 @@ static int rtl8169_close(struct net_devi
 	struct rtl8169_private *tp = netdev_priv(dev);
 	struct pci_dev *pdev = tp->pci_dev;
 
+	pm_runtime_get_sync(&pdev->dev);
+
 	/* update counters before going down */
 	rtl8169_update_counters(dev);
 
@@ -4741,6 +4780,8 @@ static int rtl8169_close(struct net_devi
 	tp->TxDescArray = NULL;
 	tp->RxDescArray = NULL;
 
+	pm_runtime_put_noidle(&pdev->dev);
+
 	return 0;
 }
 
@@ -4844,18 +4885,71 @@ static int rtl8169_suspend(struct device
 	return 0;
 }
 
+static void __rtl8169_resume(struct net_device *dev)
+{
+	netif_device_attach(dev);
+	rtl8169_schedule_work(dev, rtl8169_reset_task);
+}
+
 static int rtl8169_resume(struct device *device)
 {
 	struct pci_dev *pdev = to_pci_dev(device);
 	struct net_device *dev = pci_get_drvdata(pdev);
 
-	if (!netif_running(dev))
-		goto out;
+	if (netif_running(dev))
+		__rtl8169_resume(dev);
 
-	netif_device_attach(dev);
+	return 0;
+}
 
-	rtl8169_schedule_work(dev, rtl8169_reset_task);
-out:
+static int rtl8169_runtime_suspend(struct device *device)
+{
+	struct pci_dev *pdev = to_pci_dev(device);
+	struct net_device *dev = pci_get_drvdata(pdev);
+	struct rtl8169_private *tp = netdev_priv(dev);
+
+	if (!tp->TxDescArray)
+		return 0;
+
+	spin_lock_irq(&tp->lock);
+	tp->saved_wolopts = __rtl8169_get_wol(tp);
+	__rtl8169_set_wol(tp, WAKE_ANY);
+	spin_unlock_irq(&tp->lock);
+
+	rtl8169_net_suspend(dev);
+
+	return 0;
+}
+
+static int rtl8169_runtime_resume(struct device *device)
+{
+	struct pci_dev *pdev = to_pci_dev(device);
+	struct net_device *dev = pci_get_drvdata(pdev);
+	struct rtl8169_private *tp = netdev_priv(dev);
+
+	if (!tp->TxDescArray)
+		return 0;
+
+	spin_lock_irq(&tp->lock);
+	__rtl8169_set_wol(tp, tp->saved_wolopts);
+	tp->saved_wolopts = 0;
+	spin_unlock_irq(&tp->lock);
+
+	__rtl8169_resume(dev);
+
+	return 0;
+}
+
+static int rtl8169_runtime_idle(struct device *device)
+{
+	struct pci_dev *pdev = to_pci_dev(device);
+	struct net_device *dev = pci_get_drvdata(pdev);
+	struct rtl8169_private *tp = netdev_priv(dev);
+
+	if (tp->TxDescArray)
+		return -EBUSY;
+
+	pm_schedule_suspend(device, MSEC_PER_SEC);
 	return 0;
 }
 
@@ -4866,6 +4960,9 @@ static const struct dev_pm_ops rtl8169_p
 	.thaw = rtl8169_resume,
 	.poweroff = rtl8169_suspend,
 	.restore = rtl8169_resume,
+	.runtime_suspend = rtl8169_runtime_suspend,
+	.runtime_resume = rtl8169_runtime_resume,
+	.runtime_idle = rtl8169_runtime_idle,
 };
 
 #define RTL8169_PM_OPS	(&rtl8169_pm_ops)

^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH][RFC] e1000e: Add basic runtime PM support (rev. 3) (was: [PATCH 0/12] PCI run-time PM support (rev. 2))
  2010-01-01 19:03   ` [PATCH][RFC] e1000e: Add basic runtime PM support (rev. 2) " Rafael J. Wysocki
  2010-01-01 21:51     ` [PATCH][RFC] e1000e: Add basic runtime PM support (rev. 3) " Rafael J. Wysocki
@ 2010-01-01 21:51     ` Rafael J. Wysocki
  1 sibling, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2010-01-01 21:51 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Matthew Garrett, Len Brown, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu, Jeff Kirsher, e1000-devel

On Friday 01 January 2010, Rafael J. Wysocki wrote:
> On Friday 01 January 2010, Rafael J. Wysocki wrote:
> > On Sunday 27 December 2009, Rafael J. Wysocki wrote:
> > > Hi,
> > > 
> > > The following (updated) series of patches provides preliminary run-time power
> > > management support for PCI devices through ACPI and/or the native PCIe PME.
> > > 
> > > Some patches have been modified since the previous iteration, one patch has
> > > been merged and there's one more.
> > > 
> > > I've tested this patchset with the native PCIe PME mechanism using the r8169
> > > driver on the MSI Wind U-100 (see the last patch for details) and with the ACPI
> > > mechanism using the e1000e driver on the Toshiba Portege R500 (the patch still
> > > requires some work to be shown in public ;-)).
> > 
> > The e1000e patch is now in a better shape IMO, at least it worked for me in all
> > conditions I tested it, so it is appended below.
> 
> This one was actually buggy, as it triggered a netdevice.h BUG_ON() during
> resume from suspend to RAM with network cable attached.
> 
> Appended is a new version that doesn't have this problem and enables runtime PM
> in _probe(), so the device can be put into the low power state if _open()
> hasn't been called yet (or after _close()).

This one also has a few problems (triggers a warning about an already freed
interrupt being freed again during suspend to RAM with the adapter suspended at
run time if it is maganed by NetworkManager and doesn't put the adapter into
the low power state if the link is not present from the start), so appended is
a fixed one (at least it doesn't show any more problems in my testing).

Rafael

---
From: Rafael J. Wysocki <rjw@sisk.pl>
Subject: PCI / PM / e1000e: Add basic runtime PM support (rev. 3)

Use the PCI run-time power management framework to add simplified
run-time PM support to the e1000e driver.  Namely, make the driver
suspend the device when the link is off and set it up for generating
wake-up event after the link has been detected again.

Based on a patch from Matthew Garrett.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
 drivers/net/e1000e/netdev.c |  141 +++++++++++++++++++++++++++++++++++---------
 1 file changed, 115 insertions(+), 26 deletions(-)

Index: linux-2.6/drivers/net/e1000e/netdev.c
===================================================================
--- linux-2.6.orig/drivers/net/e1000e/netdev.c
+++ linux-2.6/drivers/net/e1000e/netdev.c
@@ -44,6 +44,7 @@
 #include <linux/cpu.h>
 #include <linux/smp.h>
 #include <linux/pm_qos_params.h>
+#include <linux/pm_runtime.h>
 #include <linux/aer.h>
 
 #include "e1000.h"
@@ -3093,12 +3094,15 @@ static int e1000_open(struct net_device 
 {
 	struct e1000_adapter *adapter = netdev_priv(netdev);
 	struct e1000_hw *hw = &adapter->hw;
+	struct pci_dev *pdev = adapter->pdev;
 	int err;
 
 	/* disallow open during test */
 	if (test_bit(__E1000_TESTING, &adapter->state))
 		return -EBUSY;
 
+	pm_runtime_get_sync(&pdev->dev);
+
 	netif_carrier_off(netdev);
 
 	/* allocate transmit descriptors */
@@ -3159,6 +3163,10 @@ static int e1000_open(struct net_device 
 
 	netif_start_queue(netdev);
 
+	pm_runtime_put_noidle(&pdev->dev);
+	if (!e1000_has_link(adapter))
+		pm_schedule_suspend(&pdev->dev, 100);
+
 	/* fire a link status change interrupt to start the watchdog */
 	ew32(ICS, E1000_ICS_LSC);
 
@@ -3172,6 +3180,7 @@ err_setup_rx:
 	e1000e_free_tx_resources(adapter);
 err_setup_tx:
 	e1000e_reset(adapter);
+	pm_runtime_put_noidle(&pdev->dev);
 
 	return err;
 }
@@ -3190,11 +3199,17 @@ err_setup_tx:
 static int e1000_close(struct net_device *netdev)
 {
 	struct e1000_adapter *adapter = netdev_priv(netdev);
+	struct pci_dev *pdev = adapter->pdev;
 
 	WARN_ON(test_bit(__E1000_RESETTING, &adapter->state));
-	e1000e_down(adapter);
+
+	pm_runtime_get_sync(&pdev->dev);
+
+	if (!test_bit(__E1000_DOWN, &adapter->state)) {
+		e1000e_down(adapter);
+		e1000_free_irq(adapter);
+	}
 	e1000_power_down_phy(adapter);
-	e1000_free_irq(adapter);
 
 	e1000e_free_tx_resources(adapter);
 	e1000e_free_rx_resources(adapter);
@@ -3216,6 +3231,8 @@ static int e1000_close(struct net_device
 	if (adapter->flags & FLAG_HAS_AMT)
 		e1000_release_hw_control(adapter);
 
+	pm_runtime_put_noidle(&pdev->dev);
+
 	return 0;
 }
 /**
@@ -3571,6 +3588,10 @@ static void e1000_watchdog_task(struct w
 	if (link) {
 		if (!netif_carrier_ok(netdev)) {
 			bool txb2b = 1;
+
+			/* This is to cancel scheduled suspend requests. */
+			pm_runtime_resume(netdev->dev.parent);
+
 			/* update snapshot of PHY registers on LSC */
 			e1000_phy_read_status(adapter);
 			mac->ops.get_link_up_info(&adapter->hw,
@@ -3686,6 +3707,8 @@ static void e1000_watchdog_task(struct w
 
 			if (adapter->flags & FLAG_RX_NEEDS_RESTART)
 				schedule_work(&adapter->reset_task);
+			else
+				pm_schedule_suspend(netdev->dev.parent, 100);
 		}
 	}
 
@@ -4489,13 +4512,15 @@ out:
 	return retval;
 }
 
-static int __e1000_shutdown(struct pci_dev *pdev, bool *enable_wake)
+static int __e1000_shutdown(struct pci_dev *pdev, bool *enable_wake,
+			    bool runtime)
 {
 	struct net_device *netdev = pci_get_drvdata(pdev);
 	struct e1000_adapter *adapter = netdev_priv(netdev);
 	struct e1000_hw *hw = &adapter->hw;
 	u32 ctrl, ctrl_ext, rctl, status;
-	u32 wufc = adapter->wol;
+	/* Runtime suspend should only enable wakeup for link changes */
+	u32 wufc = runtime ? E1000_WUFC_LNKC : adapter->wol;
 	int retval = 0;
 
 	netif_device_detach(netdev);
@@ -4653,40 +4678,59 @@ static void e1000e_disable_l1aspm(struct
 }
 
 #ifdef CONFIG_PM
-static int e1000_suspend(struct pci_dev *pdev, pm_message_t state)
+static bool e1000e_pm_ready(struct e1000_adapter *adapter)
+{
+	return !!adapter->tx_ring->buffer_info;
+}
+
+static int e1000_idle(struct device *dev)
 {
+	struct pci_dev *pdev = to_pci_dev(dev);
+	struct net_device *netdev = pci_get_drvdata(pdev);
+	struct e1000_adapter *adapter = netdev_priv(netdev);
+
+	/*  If we're active, prevent the driver core from changing our state */
+	if (e1000e_pm_ready(adapter))
+		return -EBUSY;
+
+	pm_schedule_suspend(dev, MSEC_PER_SEC);
+	return 0;
+}
+
+static int e1000_suspend(struct device *dev)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
 	int retval;
 	bool wake;
 
-	retval = __e1000_shutdown(pdev, &wake);
+	retval = __e1000_shutdown(pdev, &wake, false);
 	if (!retval)
 		e1000_complete_shutdown(pdev, true, wake);
 
 	return retval;
 }
 
-static int e1000_resume(struct pci_dev *pdev)
+static int e1000_runtime_suspend(struct device *dev)
 {
+	struct pci_dev *pdev = to_pci_dev(dev);
 	struct net_device *netdev = pci_get_drvdata(pdev);
 	struct e1000_adapter *adapter = netdev_priv(netdev);
-	struct e1000_hw *hw = &adapter->hw;
-	u32 err;
+	bool wake;
 
-	pci_set_power_state(pdev, PCI_D0);
-	pci_restore_state(pdev);
-	e1000e_disable_l1aspm(pdev);
+	if (e1000e_pm_ready(adapter))
+		return __e1000_shutdown(pdev, &wake, true);
 
-	err = pci_enable_device_mem(pdev);
-	if (err) {
-		dev_err(&pdev->dev,
-			"Cannot enable PCI device from suspend\n");
-		return err;
-	}
+	return 0;
+}
 
-	pci_set_master(pdev);
+static int __e1000_resume(struct pci_dev *pdev)
+{
+	struct net_device *netdev = pci_get_drvdata(pdev);
+	struct e1000_adapter *adapter = netdev_priv(netdev);
+	struct e1000_hw *hw = &adapter->hw;
+	u32 err;
 
-	pci_enable_wake(pdev, PCI_D3hot, 0);
-	pci_enable_wake(pdev, PCI_D3cold, 0);
+	e1000e_disable_l1aspm(pdev);
 
 	e1000e_set_interrupt_capability(adapter);
 	if (netif_running(netdev)) {
@@ -4745,13 +4789,30 @@ static int e1000_resume(struct pci_dev *
 
 	return 0;
 }
+
+static int e1000_resume(struct device *dev)
+{
+	return __e1000_resume(to_pci_dev(dev));
+}
+
+static int e1000_runtime_resume(struct device *dev)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
+	struct net_device *netdev = pci_get_drvdata(pdev);
+	struct e1000_adapter *adapter = netdev_priv(netdev);
+
+	if (e1000e_pm_ready(adapter))
+		return __e1000_resume(pdev);
+
+	return 0;
+}
 #endif
 
 static void e1000_shutdown(struct pci_dev *pdev)
 {
 	bool wake = false;
 
-	__e1000_shutdown(pdev, &wake);
+	__e1000_shutdown(pdev, &wake, false);
 
 	if (system_state == SYSTEM_POWER_OFF)
 		e1000_complete_shutdown(pdev, false, wake);
@@ -5231,6 +5292,11 @@ static int __devinit e1000_probe(struct 
 
 	e1000_print_device_info(adapter);
 
+	if (pci_dev_run_wake(pdev)) {
+		pm_runtime_set_active(&pdev->dev);
+		pm_runtime_enable(&pdev->dev);
+	}
+
 	return 0;
 
 err_register:
@@ -5273,12 +5339,16 @@ static void __devexit e1000_remove(struc
 {
 	struct net_device *netdev = pci_get_drvdata(pdev);
 	struct e1000_adapter *adapter = netdev_priv(netdev);
+	bool down = test_bit(__E1000_DOWN, &adapter->state);
+
+	pm_runtime_get_sync(&pdev->dev);
 
 	/*
 	 * flush_scheduled work may reschedule our watchdog task, so
 	 * explicitly disable watchdog tasks from being rescheduled
 	 */
-	set_bit(__E1000_DOWN, &adapter->state);
+	if (!down)
+		set_bit(__E1000_DOWN, &adapter->state);
 	del_timer_sync(&adapter->watchdog_timer);
 	del_timer_sync(&adapter->phy_info_timer);
 
@@ -5292,8 +5362,17 @@ static void __devexit e1000_remove(struc
 	if (!(netdev->flags & IFF_UP))
 		e1000_power_down_phy(adapter);
 
+	/* Don't lie to e1000_close() down the road. */
+	if (!down)
+		clear_bit(__E1000_DOWN, &adapter->state);
 	unregister_netdev(netdev);
 
+	if (pci_dev_run_wake(pdev)) {
+		pm_runtime_disable(&pdev->dev);
+		pm_runtime_set_suspended(&pdev->dev);
+	}
+	pm_runtime_put_noidle(&pdev->dev);
+
 	/*
 	 * Release control of h/w to f/w.  If f/w is AMT enabled, this
 	 * would have already happened in close and is redundant.
@@ -5393,6 +5472,18 @@ static struct pci_device_id e1000_pci_tb
 };
 MODULE_DEVICE_TABLE(pci, e1000_pci_tbl);
 
+static const struct dev_pm_ops e1000_pm_ops = {
+	.suspend  = e1000_suspend,
+	.resume   = e1000_resume,
+	.freeze = e1000_suspend,
+	.thaw = e1000_resume,
+	.poweroff = e1000_suspend,
+	.restore = e1000_resume,
+	.runtime_suspend = e1000_runtime_suspend,
+	.runtime_resume = e1000_runtime_resume,
+	.runtime_idle = e1000_idle,
+};
+
 /* PCI Device API Driver */
 static struct pci_driver e1000_driver = {
 	.name     = e1000e_driver_name,
@@ -5400,9 +5491,7 @@ static struct pci_driver e1000_driver = 
 	.probe    = e1000_probe,
 	.remove   = __devexit_p(e1000_remove),
 #ifdef CONFIG_PM
-	/* Power Management Hooks */
-	.suspend  = e1000_suspend,
-	.resume   = e1000_resume,
+	.driver.pm = &e1000_pm_ops,
 #endif
 	.shutdown = e1000_shutdown,
 	.err_handler = &e1000_err_handler

^ permalink raw reply	[flat|nested] 68+ messages in thread

* [PATCH][RFC] e1000e: Add basic runtime PM support (rev. 3) (was: [PATCH 0/12] PCI run-time PM support (rev. 2))
  2010-01-01 19:03   ` [PATCH][RFC] e1000e: Add basic runtime PM support (rev. 2) " Rafael J. Wysocki
@ 2010-01-01 21:51     ` Rafael J. Wysocki
  2010-01-01 21:51     ` Rafael J. Wysocki
  1 sibling, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2010-01-01 21:51 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: e1000-devel, Linux PCI, LKML, ACPI Devel Maling List,
	Jeff Kirsher, Francois Romieu, pm list, Matthew Garrett

On Friday 01 January 2010, Rafael J. Wysocki wrote:
> On Friday 01 January 2010, Rafael J. Wysocki wrote:
> > On Sunday 27 December 2009, Rafael J. Wysocki wrote:
> > > Hi,
> > > 
> > > The following (updated) series of patches provides preliminary run-time power
> > > management support for PCI devices through ACPI and/or the native PCIe PME.
> > > 
> > > Some patches have been modified since the previous iteration, one patch has
> > > been merged and there's one more.
> > > 
> > > I've tested this patchset with the native PCIe PME mechanism using the r8169
> > > driver on the MSI Wind U-100 (see the last patch for details) and with the ACPI
> > > mechanism using the e1000e driver on the Toshiba Portege R500 (the patch still
> > > requires some work to be shown in public ;-)).
> > 
> > The e1000e patch is now in a better shape IMO, at least it worked for me in all
> > conditions I tested it, so it is appended below.
> 
> This one was actually buggy, as it triggered a netdevice.h BUG_ON() during
> resume from suspend to RAM with network cable attached.
> 
> Appended is a new version that doesn't have this problem and enables runtime PM
> in _probe(), so the device can be put into the low power state if _open()
> hasn't been called yet (or after _close()).

This one also has a few problems (triggers a warning about an already freed
interrupt being freed again during suspend to RAM with the adapter suspended at
run time if it is maganed by NetworkManager and doesn't put the adapter into
the low power state if the link is not present from the start), so appended is
a fixed one (at least it doesn't show any more problems in my testing).

Rafael

---
From: Rafael J. Wysocki <rjw@sisk.pl>
Subject: PCI / PM / e1000e: Add basic runtime PM support (rev. 3)

Use the PCI run-time power management framework to add simplified
run-time PM support to the e1000e driver.  Namely, make the driver
suspend the device when the link is off and set it up for generating
wake-up event after the link has been detected again.

Based on a patch from Matthew Garrett.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
 drivers/net/e1000e/netdev.c |  141 +++++++++++++++++++++++++++++++++++---------
 1 file changed, 115 insertions(+), 26 deletions(-)

Index: linux-2.6/drivers/net/e1000e/netdev.c
===================================================================
--- linux-2.6.orig/drivers/net/e1000e/netdev.c
+++ linux-2.6/drivers/net/e1000e/netdev.c
@@ -44,6 +44,7 @@
 #include <linux/cpu.h>
 #include <linux/smp.h>
 #include <linux/pm_qos_params.h>
+#include <linux/pm_runtime.h>
 #include <linux/aer.h>
 
 #include "e1000.h"
@@ -3093,12 +3094,15 @@ static int e1000_open(struct net_device 
 {
 	struct e1000_adapter *adapter = netdev_priv(netdev);
 	struct e1000_hw *hw = &adapter->hw;
+	struct pci_dev *pdev = adapter->pdev;
 	int err;
 
 	/* disallow open during test */
 	if (test_bit(__E1000_TESTING, &adapter->state))
 		return -EBUSY;
 
+	pm_runtime_get_sync(&pdev->dev);
+
 	netif_carrier_off(netdev);
 
 	/* allocate transmit descriptors */
@@ -3159,6 +3163,10 @@ static int e1000_open(struct net_device 
 
 	netif_start_queue(netdev);
 
+	pm_runtime_put_noidle(&pdev->dev);
+	if (!e1000_has_link(adapter))
+		pm_schedule_suspend(&pdev->dev, 100);
+
 	/* fire a link status change interrupt to start the watchdog */
 	ew32(ICS, E1000_ICS_LSC);
 
@@ -3172,6 +3180,7 @@ err_setup_rx:
 	e1000e_free_tx_resources(adapter);
 err_setup_tx:
 	e1000e_reset(adapter);
+	pm_runtime_put_noidle(&pdev->dev);
 
 	return err;
 }
@@ -3190,11 +3199,17 @@ err_setup_tx:
 static int e1000_close(struct net_device *netdev)
 {
 	struct e1000_adapter *adapter = netdev_priv(netdev);
+	struct pci_dev *pdev = adapter->pdev;
 
 	WARN_ON(test_bit(__E1000_RESETTING, &adapter->state));
-	e1000e_down(adapter);
+
+	pm_runtime_get_sync(&pdev->dev);
+
+	if (!test_bit(__E1000_DOWN, &adapter->state)) {
+		e1000e_down(adapter);
+		e1000_free_irq(adapter);
+	}
 	e1000_power_down_phy(adapter);
-	e1000_free_irq(adapter);
 
 	e1000e_free_tx_resources(adapter);
 	e1000e_free_rx_resources(adapter);
@@ -3216,6 +3231,8 @@ static int e1000_close(struct net_device
 	if (adapter->flags & FLAG_HAS_AMT)
 		e1000_release_hw_control(adapter);
 
+	pm_runtime_put_noidle(&pdev->dev);
+
 	return 0;
 }
 /**
@@ -3571,6 +3588,10 @@ static void e1000_watchdog_task(struct w
 	if (link) {
 		if (!netif_carrier_ok(netdev)) {
 			bool txb2b = 1;
+
+			/* This is to cancel scheduled suspend requests. */
+			pm_runtime_resume(netdev->dev.parent);
+
 			/* update snapshot of PHY registers on LSC */
 			e1000_phy_read_status(adapter);
 			mac->ops.get_link_up_info(&adapter->hw,
@@ -3686,6 +3707,8 @@ static void e1000_watchdog_task(struct w
 
 			if (adapter->flags & FLAG_RX_NEEDS_RESTART)
 				schedule_work(&adapter->reset_task);
+			else
+				pm_schedule_suspend(netdev->dev.parent, 100);
 		}
 	}
 
@@ -4489,13 +4512,15 @@ out:
 	return retval;
 }
 
-static int __e1000_shutdown(struct pci_dev *pdev, bool *enable_wake)
+static int __e1000_shutdown(struct pci_dev *pdev, bool *enable_wake,
+			    bool runtime)
 {
 	struct net_device *netdev = pci_get_drvdata(pdev);
 	struct e1000_adapter *adapter = netdev_priv(netdev);
 	struct e1000_hw *hw = &adapter->hw;
 	u32 ctrl, ctrl_ext, rctl, status;
-	u32 wufc = adapter->wol;
+	/* Runtime suspend should only enable wakeup for link changes */
+	u32 wufc = runtime ? E1000_WUFC_LNKC : adapter->wol;
 	int retval = 0;
 
 	netif_device_detach(netdev);
@@ -4653,40 +4678,59 @@ static void e1000e_disable_l1aspm(struct
 }
 
 #ifdef CONFIG_PM
-static int e1000_suspend(struct pci_dev *pdev, pm_message_t state)
+static bool e1000e_pm_ready(struct e1000_adapter *adapter)
+{
+	return !!adapter->tx_ring->buffer_info;
+}
+
+static int e1000_idle(struct device *dev)
 {
+	struct pci_dev *pdev = to_pci_dev(dev);
+	struct net_device *netdev = pci_get_drvdata(pdev);
+	struct e1000_adapter *adapter = netdev_priv(netdev);
+
+	/*  If we're active, prevent the driver core from changing our state */
+	if (e1000e_pm_ready(adapter))
+		return -EBUSY;
+
+	pm_schedule_suspend(dev, MSEC_PER_SEC);
+	return 0;
+}
+
+static int e1000_suspend(struct device *dev)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
 	int retval;
 	bool wake;
 
-	retval = __e1000_shutdown(pdev, &wake);
+	retval = __e1000_shutdown(pdev, &wake, false);
 	if (!retval)
 		e1000_complete_shutdown(pdev, true, wake);
 
 	return retval;
 }
 
-static int e1000_resume(struct pci_dev *pdev)
+static int e1000_runtime_suspend(struct device *dev)
 {
+	struct pci_dev *pdev = to_pci_dev(dev);
 	struct net_device *netdev = pci_get_drvdata(pdev);
 	struct e1000_adapter *adapter = netdev_priv(netdev);
-	struct e1000_hw *hw = &adapter->hw;
-	u32 err;
+	bool wake;
 
-	pci_set_power_state(pdev, PCI_D0);
-	pci_restore_state(pdev);
-	e1000e_disable_l1aspm(pdev);
+	if (e1000e_pm_ready(adapter))
+		return __e1000_shutdown(pdev, &wake, true);
 
-	err = pci_enable_device_mem(pdev);
-	if (err) {
-		dev_err(&pdev->dev,
-			"Cannot enable PCI device from suspend\n");
-		return err;
-	}
+	return 0;
+}
 
-	pci_set_master(pdev);
+static int __e1000_resume(struct pci_dev *pdev)
+{
+	struct net_device *netdev = pci_get_drvdata(pdev);
+	struct e1000_adapter *adapter = netdev_priv(netdev);
+	struct e1000_hw *hw = &adapter->hw;
+	u32 err;
 
-	pci_enable_wake(pdev, PCI_D3hot, 0);
-	pci_enable_wake(pdev, PCI_D3cold, 0);
+	e1000e_disable_l1aspm(pdev);
 
 	e1000e_set_interrupt_capability(adapter);
 	if (netif_running(netdev)) {
@@ -4745,13 +4789,30 @@ static int e1000_resume(struct pci_dev *
 
 	return 0;
 }
+
+static int e1000_resume(struct device *dev)
+{
+	return __e1000_resume(to_pci_dev(dev));
+}
+
+static int e1000_runtime_resume(struct device *dev)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
+	struct net_device *netdev = pci_get_drvdata(pdev);
+	struct e1000_adapter *adapter = netdev_priv(netdev);
+
+	if (e1000e_pm_ready(adapter))
+		return __e1000_resume(pdev);
+
+	return 0;
+}
 #endif
 
 static void e1000_shutdown(struct pci_dev *pdev)
 {
 	bool wake = false;
 
-	__e1000_shutdown(pdev, &wake);
+	__e1000_shutdown(pdev, &wake, false);
 
 	if (system_state == SYSTEM_POWER_OFF)
 		e1000_complete_shutdown(pdev, false, wake);
@@ -5231,6 +5292,11 @@ static int __devinit e1000_probe(struct 
 
 	e1000_print_device_info(adapter);
 
+	if (pci_dev_run_wake(pdev)) {
+		pm_runtime_set_active(&pdev->dev);
+		pm_runtime_enable(&pdev->dev);
+	}
+
 	return 0;
 
 err_register:
@@ -5273,12 +5339,16 @@ static void __devexit e1000_remove(struc
 {
 	struct net_device *netdev = pci_get_drvdata(pdev);
 	struct e1000_adapter *adapter = netdev_priv(netdev);
+	bool down = test_bit(__E1000_DOWN, &adapter->state);
+
+	pm_runtime_get_sync(&pdev->dev);
 
 	/*
 	 * flush_scheduled work may reschedule our watchdog task, so
 	 * explicitly disable watchdog tasks from being rescheduled
 	 */
-	set_bit(__E1000_DOWN, &adapter->state);
+	if (!down)
+		set_bit(__E1000_DOWN, &adapter->state);
 	del_timer_sync(&adapter->watchdog_timer);
 	del_timer_sync(&adapter->phy_info_timer);
 
@@ -5292,8 +5362,17 @@ static void __devexit e1000_remove(struc
 	if (!(netdev->flags & IFF_UP))
 		e1000_power_down_phy(adapter);
 
+	/* Don't lie to e1000_close() down the road. */
+	if (!down)
+		clear_bit(__E1000_DOWN, &adapter->state);
 	unregister_netdev(netdev);
 
+	if (pci_dev_run_wake(pdev)) {
+		pm_runtime_disable(&pdev->dev);
+		pm_runtime_set_suspended(&pdev->dev);
+	}
+	pm_runtime_put_noidle(&pdev->dev);
+
 	/*
 	 * Release control of h/w to f/w.  If f/w is AMT enabled, this
 	 * would have already happened in close and is redundant.
@@ -5393,6 +5472,18 @@ static struct pci_device_id e1000_pci_tb
 };
 MODULE_DEVICE_TABLE(pci, e1000_pci_tbl);
 
+static const struct dev_pm_ops e1000_pm_ops = {
+	.suspend  = e1000_suspend,
+	.resume   = e1000_resume,
+	.freeze = e1000_suspend,
+	.thaw = e1000_resume,
+	.poweroff = e1000_suspend,
+	.restore = e1000_resume,
+	.runtime_suspend = e1000_runtime_suspend,
+	.runtime_resume = e1000_runtime_resume,
+	.runtime_idle = e1000_idle,
+};
+
 /* PCI Device API Driver */
 static struct pci_driver e1000_driver = {
 	.name     = e1000e_driver_name,
@@ -5400,9 +5491,7 @@ static struct pci_driver e1000_driver = 
 	.probe    = e1000_probe,
 	.remove   = __devexit_p(e1000_remove),
 #ifdef CONFIG_PM
-	/* Power Management Hooks */
-	.suspend  = e1000_suspend,
-	.resume   = e1000_resume,
+	.driver.pm = &e1000_pm_ops,
 #endif
 	.shutdown = e1000_shutdown,
 	.err_handler = &e1000_err_handler

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 2/12] PCI / PM: Propagate wake-up enable for PCIe devices too
  2009-12-27 20:00 ` [PATCH 2/12] PCI / PM: Propagate wake-up enable for PCIe devices too Rafael J. Wysocki
@ 2010-01-04 23:40   ` Jesse Barnes
  2010-01-05 21:27     ` Rafael J. Wysocki
  2010-01-05 21:27     ` Rafael J. Wysocki
  2010-01-04 23:40   ` Jesse Barnes
  1 sibling, 2 replies; 68+ messages in thread
From: Jesse Barnes @ 2010-01-04 23:40 UTC (permalink / raw)
  To: Rafael J. Wysocki
  Cc: Matthew Garrett, Len Brown, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu

On Sun, 27 Dec 2009 21:00:33 +0100
"Rafael J. Wysocki" <rjw@sisk.pl> wrote:

> From: Rafael J. Wysocki <rjw@sisk.pl>
> 
> Having read the PM part of the PCIe 2.0 specification more carefully
> I think that it was a mistake to restrict the wake-up enable
> propagation to non-PCIe devices, because if we do not request
> control of the root ports' PME registers via OSC, PCIe PME is
> supposed to be handled by the platform, just like the non-PCIe PME.
> Even if we do that, the wake-up propagation is done to allow the
> devices to wake up the system from sleep states which involves the
> platform anyway, so it won't hurt.
> 
> Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
> ---
>  drivers/pci/pci-acpi.c |   10 ++--------
>  1 file changed, 2 insertions(+), 8 deletions(-)
> 
> Index: linux-2.6/drivers/pci/pci-acpi.c
> ===================================================================
> --- linux-2.6.orig/drivers/pci/pci-acpi.c
> +++ linux-2.6/drivers/pci/pci-acpi.c
> @@ -112,11 +112,7 @@ static bool acpi_pci_can_wakeup(struct p
>  static void acpi_pci_propagate_wakeup_enable(struct pci_bus *bus,
> bool enable) {
>  	while (bus->parent) {
> -		struct pci_dev *bridge = bus->self;
> -		int ret;
> -
> -		ret = acpi_pm_device_sleep_wake(&bridge->dev,
> enable);
> -		if (!ret || pci_is_pcie(bridge))
> +		if (!acpi_pm_device_sleep_wake(&bus->self->dev,
> enable)) return;
>  		bus = bus->parent;
>  	}
> @@ -131,9 +127,7 @@ static int acpi_pci_sleep_wake(struct pc
>  	if (acpi_pci_can_wakeup(dev))
>  		return acpi_pm_device_sleep_wake(&dev->dev, enable);
>  
> -	if (!pci_is_pcie(dev))
> -		acpi_pci_propagate_wakeup_enable(dev->bus, enable);
> -
> +	acpi_pci_propagate_wakeup_enable(dev->bus, enable);
>  	return 0;
>  }

Seems ok to try it out, but as usual with this sort of thing, the real
question is, "What does Windows do?", since wakeup enable ACPI support
for PCIe devices is likely broken unless Windows uses it.

-- 
Jesse Barnes, Intel Open Source Technology Center

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 2/12] PCI / PM: Propagate wake-up enable for PCIe devices too
  2009-12-27 20:00 ` [PATCH 2/12] PCI / PM: Propagate wake-up enable for PCIe devices too Rafael J. Wysocki
  2010-01-04 23:40   ` Jesse Barnes
@ 2010-01-04 23:40   ` Jesse Barnes
  1 sibling, 0 replies; 68+ messages in thread
From: Jesse Barnes @ 2010-01-04 23:40 UTC (permalink / raw)
  To: Rafael J. Wysocki
  Cc: Oliver, Linux PCI, LKML, ACPI Devel Maling List, Shaohua,
	Francois Romieu, pm list, Matthew Garrett

On Sun, 27 Dec 2009 21:00:33 +0100
"Rafael J. Wysocki" <rjw@sisk.pl> wrote:

> From: Rafael J. Wysocki <rjw@sisk.pl>
> 
> Having read the PM part of the PCIe 2.0 specification more carefully
> I think that it was a mistake to restrict the wake-up enable
> propagation to non-PCIe devices, because if we do not request
> control of the root ports' PME registers via OSC, PCIe PME is
> supposed to be handled by the platform, just like the non-PCIe PME.
> Even if we do that, the wake-up propagation is done to allow the
> devices to wake up the system from sleep states which involves the
> platform anyway, so it won't hurt.
> 
> Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
> ---
>  drivers/pci/pci-acpi.c |   10 ++--------
>  1 file changed, 2 insertions(+), 8 deletions(-)
> 
> Index: linux-2.6/drivers/pci/pci-acpi.c
> ===================================================================
> --- linux-2.6.orig/drivers/pci/pci-acpi.c
> +++ linux-2.6/drivers/pci/pci-acpi.c
> @@ -112,11 +112,7 @@ static bool acpi_pci_can_wakeup(struct p
>  static void acpi_pci_propagate_wakeup_enable(struct pci_bus *bus,
> bool enable) {
>  	while (bus->parent) {
> -		struct pci_dev *bridge = bus->self;
> -		int ret;
> -
> -		ret = acpi_pm_device_sleep_wake(&bridge->dev,
> enable);
> -		if (!ret || pci_is_pcie(bridge))
> +		if (!acpi_pm_device_sleep_wake(&bus->self->dev,
> enable)) return;
>  		bus = bus->parent;
>  	}
> @@ -131,9 +127,7 @@ static int acpi_pci_sleep_wake(struct pc
>  	if (acpi_pci_can_wakeup(dev))
>  		return acpi_pm_device_sleep_wake(&dev->dev, enable);
>  
> -	if (!pci_is_pcie(dev))
> -		acpi_pci_propagate_wakeup_enable(dev->bus, enable);
> -
> +	acpi_pci_propagate_wakeup_enable(dev->bus, enable);
>  	return 0;
>  }

Seems ok to try it out, but as usual with this sort of thing, the real
question is, "What does Windows do?", since wakeup enable ACPI support
for PCIe devices is likely broken unless Windows uses it.

-- 
Jesse Barnes, Intel Open Source Technology Center

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 2/12] PCI / PM: Propagate wake-up enable for PCIe devices too
  2010-01-04 23:40   ` Jesse Barnes
  2010-01-05 21:27     ` Rafael J. Wysocki
@ 2010-01-05 21:27     ` Rafael J. Wysocki
  1 sibling, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2010-01-05 21:27 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Matthew Garrett, Len Brown, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu

On Tuesday 05 January 2010, Jesse Barnes wrote:
> On Sun, 27 Dec 2009 21:00:33 +0100
> "Rafael J. Wysocki" <rjw@sisk.pl> wrote:
> 
> > From: Rafael J. Wysocki <rjw@sisk.pl>
> > 
> > Having read the PM part of the PCIe 2.0 specification more carefully
> > I think that it was a mistake to restrict the wake-up enable
> > propagation to non-PCIe devices, because if we do not request
> > control of the root ports' PME registers via OSC, PCIe PME is
> > supposed to be handled by the platform, just like the non-PCIe PME.
> > Even if we do that, the wake-up propagation is done to allow the
> > devices to wake up the system from sleep states which involves the
> > platform anyway, so it won't hurt.
> > 
> > Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
> > ---
> >  drivers/pci/pci-acpi.c |   10 ++--------
> >  1 file changed, 2 insertions(+), 8 deletions(-)
> > 
> > Index: linux-2.6/drivers/pci/pci-acpi.c
> > ===================================================================
> > --- linux-2.6.orig/drivers/pci/pci-acpi.c
> > +++ linux-2.6/drivers/pci/pci-acpi.c
> > @@ -112,11 +112,7 @@ static bool acpi_pci_can_wakeup(struct p
> >  static void acpi_pci_propagate_wakeup_enable(struct pci_bus *bus,
> > bool enable) {
> >  	while (bus->parent) {
> > -		struct pci_dev *bridge = bus->self;
> > -		int ret;
> > -
> > -		ret = acpi_pm_device_sleep_wake(&bridge->dev,
> > enable);
> > -		if (!ret || pci_is_pcie(bridge))
> > +		if (!acpi_pm_device_sleep_wake(&bus->self->dev,
> > enable)) return;
> >  		bus = bus->parent;
> >  	}
> > @@ -131,9 +127,7 @@ static int acpi_pci_sleep_wake(struct pc
> >  	if (acpi_pci_can_wakeup(dev))
> >  		return acpi_pm_device_sleep_wake(&dev->dev, enable);
> >  
> > -	if (!pci_is_pcie(dev))
> > -		acpi_pci_propagate_wakeup_enable(dev->bus, enable);
> > -
> > +	acpi_pci_propagate_wakeup_enable(dev->bus, enable);
> >  	return 0;
> >  }
> 
> Seems ok to try it out, but as usual with this sort of thing, the real
> question is, "What does Windows do?", since wakeup enable ACPI support
> for PCIe devices is likely broken unless Windows uses it.

Well, that's in the Linus' tree already, so I guess we're going to learn. ;-)

Seriously, I think that Windows actually uses ACPI wake-up support for PCIe
devices.  On my Toshiba Portege R500 the BIOS doesn't even allow us to use
the native PCIe PME mechanism directly, so the patch is correct IMO.

Rafael

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 2/12] PCI / PM: Propagate wake-up enable for PCIe devices too
  2010-01-04 23:40   ` Jesse Barnes
@ 2010-01-05 21:27     ` Rafael J. Wysocki
  2010-01-05 21:27     ` Rafael J. Wysocki
  1 sibling, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2010-01-05 21:27 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Linux PCI, LKML, ACPI Devel Maling List, Francois Romieu,
	pm list, Matthew Garrett

On Tuesday 05 January 2010, Jesse Barnes wrote:
> On Sun, 27 Dec 2009 21:00:33 +0100
> "Rafael J. Wysocki" <rjw@sisk.pl> wrote:
> 
> > From: Rafael J. Wysocki <rjw@sisk.pl>
> > 
> > Having read the PM part of the PCIe 2.0 specification more carefully
> > I think that it was a mistake to restrict the wake-up enable
> > propagation to non-PCIe devices, because if we do not request
> > control of the root ports' PME registers via OSC, PCIe PME is
> > supposed to be handled by the platform, just like the non-PCIe PME.
> > Even if we do that, the wake-up propagation is done to allow the
> > devices to wake up the system from sleep states which involves the
> > platform anyway, so it won't hurt.
> > 
> > Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
> > ---
> >  drivers/pci/pci-acpi.c |   10 ++--------
> >  1 file changed, 2 insertions(+), 8 deletions(-)
> > 
> > Index: linux-2.6/drivers/pci/pci-acpi.c
> > ===================================================================
> > --- linux-2.6.orig/drivers/pci/pci-acpi.c
> > +++ linux-2.6/drivers/pci/pci-acpi.c
> > @@ -112,11 +112,7 @@ static bool acpi_pci_can_wakeup(struct p
> >  static void acpi_pci_propagate_wakeup_enable(struct pci_bus *bus,
> > bool enable) {
> >  	while (bus->parent) {
> > -		struct pci_dev *bridge = bus->self;
> > -		int ret;
> > -
> > -		ret = acpi_pm_device_sleep_wake(&bridge->dev,
> > enable);
> > -		if (!ret || pci_is_pcie(bridge))
> > +		if (!acpi_pm_device_sleep_wake(&bus->self->dev,
> > enable)) return;
> >  		bus = bus->parent;
> >  	}
> > @@ -131,9 +127,7 @@ static int acpi_pci_sleep_wake(struct pc
> >  	if (acpi_pci_can_wakeup(dev))
> >  		return acpi_pm_device_sleep_wake(&dev->dev, enable);
> >  
> > -	if (!pci_is_pcie(dev))
> > -		acpi_pci_propagate_wakeup_enable(dev->bus, enable);
> > -
> > +	acpi_pci_propagate_wakeup_enable(dev->bus, enable);
> >  	return 0;
> >  }
> 
> Seems ok to try it out, but as usual with this sort of thing, the real
> question is, "What does Windows do?", since wakeup enable ACPI support
> for PCIe devices is likely broken unless Windows uses it.

Well, that's in the Linus' tree already, so I guess we're going to learn. ;-)

Seriously, I think that Windows actually uses ACPI wake-up support for PCIe
devices.  On my Toshiba Portege R500 the BIOS doesn't even allow us to use
the native PCIe PME mechanism directly, so the patch is correct IMO.

Rafael

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 1/12] PCI PM: Add function for checking PME status of devices
  2009-12-27 19:59   ` Rafael J. Wysocki
  (?)
  (?)
@ 2010-01-06 21:46   ` Jesse Barnes
  -1 siblings, 0 replies; 68+ messages in thread
From: Jesse Barnes @ 2010-01-06 21:46 UTC (permalink / raw)
  To: Rafael J. Wysocki
  Cc: Matthew Garrett, Len Brown, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu

On Sun, 27 Dec 2009 20:59:06 +0100
"Rafael J. Wysocki" <rjw@sisk.pl> wrote:

> From: Rafael J. Wysocki <rjw@sisk.pl>
> 
> Add function pci_check_pme_status() that will check the PME status
> bit of given device and clear it along with the PME enable bit.  It
> will be necessary for PCI run-time power management.
> 
> Based on a patch from Shaohua Li <shaohua.li@intel.com>
> 
> Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
> ---
>  drivers/pci/pci.c |   35 +++++++++++++++++++++++++++++++++++
>  drivers/pci/pci.h |    1 +
>  2 files changed, 36 insertions(+)
> 
> Index: linux-2.6/drivers/pci/pci.h
> ===================================================================
> --- linux-2.6.orig/drivers/pci/pci.h
> +++ linux-2.6/drivers/pci/pci.h
> @@ -49,6 +49,7 @@ struct pci_platform_pm_ops {
>  extern int pci_set_platform_pm(struct pci_platform_pm_ops *ops);
>  extern void pci_update_current_state(struct pci_dev *dev,
> pci_power_t state); extern void pci_disable_enabled_device(struct
> pci_dev *dev); +extern bool pci_check_pme_status(struct pci_dev *dev);
>  extern void pci_pm_init(struct pci_dev *dev);
>  extern void platform_pci_wakeup_init(struct pci_dev *dev);
>  extern void pci_allocate_cap_save_buffers(struct pci_dev *dev);
> Index: linux-2.6/drivers/pci/pci.c
> ===================================================================
> --- linux-2.6.orig/drivers/pci/pci.c
> +++ linux-2.6/drivers/pci/pci.c
> @@ -1180,6 +1180,41 @@ int pci_set_pcie_reset_state(struct pci_
>  }
>  
>  /**
> + * pci_check_pme_status - Check if given device has generated PME.
> + * @dev: Device to check.
> + *
> + * Check the PME status of the device and if set, clear it and clear
> PME enable
> + * (if set).  Return 'true' if PME status and PME enable were both
> set or
> + * 'false' otherwise.
> + */
> +bool pci_check_pme_status(struct pci_dev *dev)
> +{
> +	int pmcsr_pos;
> +	u16 pmcsr;
> +	bool ret = false;
> +
> +	if (!dev->pm_cap)
> +		return false;
> +
> +	pmcsr_pos = dev->pm_cap + PCI_PM_CTRL;
> +	pci_read_config_word(dev, pmcsr_pos, &pmcsr);
> +	if (!(pmcsr & PCI_PM_CTRL_PME_STATUS))
> +		return false;
> +
> +	/* Clear PME status. */
> +	pmcsr |= PCI_PM_CTRL_PME_STATUS;
> +	if (pmcsr & PCI_PM_CTRL_PME_ENABLE) {
> +		/* Disable PME to avoid interrupt flood. */
> +		pmcsr &= ~PCI_PM_CTRL_PME_ENABLE;
> +		ret = true;
> +	}
> +
> +	pci_write_config_word(dev, pmcsr_pos, pmcsr);
> +
> +	return ret;
> +}
> +
> +/**
>   * pci_pme_capable - check the capability of PCI device to generate
> PME#
>   * @dev: PCI device to handle.
>   * @state: PCI state from which device will issue PME#.
> 
> 

Looks fine to me; would be especially nice if we could somehow
communicate that this function was ack'ing any outstanding PME event in
the function name too.

-- 
Jesse Barnes, Intel Open Source Technology Center

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 1/12] PCI PM: Add function for checking PME status of devices
  2009-12-27 19:59   ` Rafael J. Wysocki
  (?)
@ 2010-01-06 21:46   ` Jesse Barnes
  -1 siblings, 0 replies; 68+ messages in thread
From: Jesse Barnes @ 2010-01-06 21:46 UTC (permalink / raw)
  To: Rafael J. Wysocki
  Cc: Oliver, Linux PCI, LKML, ACPI Devel Maling List, Shaohua,
	Francois Romieu, pm list, Matthew Garrett

On Sun, 27 Dec 2009 20:59:06 +0100
"Rafael J. Wysocki" <rjw@sisk.pl> wrote:

> From: Rafael J. Wysocki <rjw@sisk.pl>
> 
> Add function pci_check_pme_status() that will check the PME status
> bit of given device and clear it along with the PME enable bit.  It
> will be necessary for PCI run-time power management.
> 
> Based on a patch from Shaohua Li <shaohua.li@intel.com>
> 
> Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
> ---
>  drivers/pci/pci.c |   35 +++++++++++++++++++++++++++++++++++
>  drivers/pci/pci.h |    1 +
>  2 files changed, 36 insertions(+)
> 
> Index: linux-2.6/drivers/pci/pci.h
> ===================================================================
> --- linux-2.6.orig/drivers/pci/pci.h
> +++ linux-2.6/drivers/pci/pci.h
> @@ -49,6 +49,7 @@ struct pci_platform_pm_ops {
>  extern int pci_set_platform_pm(struct pci_platform_pm_ops *ops);
>  extern void pci_update_current_state(struct pci_dev *dev,
> pci_power_t state); extern void pci_disable_enabled_device(struct
> pci_dev *dev); +extern bool pci_check_pme_status(struct pci_dev *dev);
>  extern void pci_pm_init(struct pci_dev *dev);
>  extern void platform_pci_wakeup_init(struct pci_dev *dev);
>  extern void pci_allocate_cap_save_buffers(struct pci_dev *dev);
> Index: linux-2.6/drivers/pci/pci.c
> ===================================================================
> --- linux-2.6.orig/drivers/pci/pci.c
> +++ linux-2.6/drivers/pci/pci.c
> @@ -1180,6 +1180,41 @@ int pci_set_pcie_reset_state(struct pci_
>  }
>  
>  /**
> + * pci_check_pme_status - Check if given device has generated PME.
> + * @dev: Device to check.
> + *
> + * Check the PME status of the device and if set, clear it and clear
> PME enable
> + * (if set).  Return 'true' if PME status and PME enable were both
> set or
> + * 'false' otherwise.
> + */
> +bool pci_check_pme_status(struct pci_dev *dev)
> +{
> +	int pmcsr_pos;
> +	u16 pmcsr;
> +	bool ret = false;
> +
> +	if (!dev->pm_cap)
> +		return false;
> +
> +	pmcsr_pos = dev->pm_cap + PCI_PM_CTRL;
> +	pci_read_config_word(dev, pmcsr_pos, &pmcsr);
> +	if (!(pmcsr & PCI_PM_CTRL_PME_STATUS))
> +		return false;
> +
> +	/* Clear PME status. */
> +	pmcsr |= PCI_PM_CTRL_PME_STATUS;
> +	if (pmcsr & PCI_PM_CTRL_PME_ENABLE) {
> +		/* Disable PME to avoid interrupt flood. */
> +		pmcsr &= ~PCI_PM_CTRL_PME_ENABLE;
> +		ret = true;
> +	}
> +
> +	pci_write_config_word(dev, pmcsr_pos, pmcsr);
> +
> +	return ret;
> +}
> +
> +/**
>   * pci_pme_capable - check the capability of PCI device to generate
> PME#
>   * @dev: PCI device to handle.
>   * @state: PCI state from which device will issue PME#.
> 
> 

Looks fine to me; would be especially nice if we could somehow
communicate that this function was ack'ing any outstanding PME event in
the function name too.

-- 
Jesse Barnes, Intel Open Source Technology Center

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 3/12] PCI PM: PCIe PME root port service driver (rev. 5)
  2009-12-27 20:01 ` Rafael J. Wysocki
  2010-01-06 21:53   ` Jesse Barnes
@ 2010-01-06 21:53   ` Jesse Barnes
  1 sibling, 0 replies; 68+ messages in thread
From: Jesse Barnes @ 2010-01-06 21:53 UTC (permalink / raw)
  To: Rafael J. Wysocki
  Cc: Matthew Garrett, Len Brown, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu

On Sun, 27 Dec 2009 21:01:24 +0100
"Rafael J. Wysocki" <rjw@sisk.pl> wrote:

> From: Rafael J. Wysocki <rjw@sisk.pl>
> 
> PCIe native PME detection mechanism is based on interrupts generated
> by root ports or event collectors every time a PCIe device sends a
> PME message upstream.
> 
> Once a PME message has been sent by an endpoint device and received
> by its root port (or event collector in the case of root complex
> integrated endpoints), the Requester ID from the message header is
> registered in the root port's Root Status register.  At the same
> time, the PME Status bit of the Root Status register is set to
> indicate that there's a PME to handle.  If PCIe PME interrupt is
> enabled for the root port, it generates an interrupt once the PME
> Status has been set.  After receiving the interrupt, the kernel can
> identify the PCIe device that generated the PME using the Requester
> ID from the root port's Root Status register. [For details, see PCI
> Express Base Specification, Rev. 2.0.]
> 
> Implement a driver for the PCIe PME root port service working in
> accordance with the above description.
> 
> Based on a patch from Shaohua Li <shaohua.li@intel.com>.

This one looks pretty nice.

-- 
Jesse Barnes, Intel Open Source Technology Center

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 3/12] PCI PM: PCIe PME root port service driver (rev. 5)
  2009-12-27 20:01 ` Rafael J. Wysocki
@ 2010-01-06 21:53   ` Jesse Barnes
  2010-01-06 21:53   ` Jesse Barnes
  1 sibling, 0 replies; 68+ messages in thread
From: Jesse Barnes @ 2010-01-06 21:53 UTC (permalink / raw)
  To: Rafael J. Wysocki
  Cc: Oliver, Linux PCI, LKML, ACPI Devel Maling List, Shaohua,
	Francois Romieu, pm list, Matthew Garrett

On Sun, 27 Dec 2009 21:01:24 +0100
"Rafael J. Wysocki" <rjw@sisk.pl> wrote:

> From: Rafael J. Wysocki <rjw@sisk.pl>
> 
> PCIe native PME detection mechanism is based on interrupts generated
> by root ports or event collectors every time a PCIe device sends a
> PME message upstream.
> 
> Once a PME message has been sent by an endpoint device and received
> by its root port (or event collector in the case of root complex
> integrated endpoints), the Requester ID from the message header is
> registered in the root port's Root Status register.  At the same
> time, the PME Status bit of the Root Status register is set to
> indicate that there's a PME to handle.  If PCIe PME interrupt is
> enabled for the root port, it generates an interrupt once the PME
> Status has been set.  After receiving the interrupt, the kernel can
> identify the PCIe device that generated the PME using the Requester
> ID from the root port's Root Status register. [For details, see PCI
> Express Base Specification, Rev. 2.0.]
> 
> Implement a driver for the PCIe PME root port service working in
> accordance with the above description.
> 
> Based on a patch from Shaohua Li <shaohua.li@intel.com>.

This one looks pretty nice.

-- 
Jesse Barnes, Intel Open Source Technology Center

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 4/12] PCI PM: Make it possible to force using INTx for PCIe PME signaling
  2009-12-27 20:02   ` Rafael J. Wysocki
  (?)
  (?)
@ 2010-01-06 21:56   ` Jesse Barnes
  2010-01-06 22:02     ` Matthew Garrett
  2010-01-06 22:02     ` Matthew Garrett
  -1 siblings, 2 replies; 68+ messages in thread
From: Jesse Barnes @ 2010-01-06 21:56 UTC (permalink / raw)
  To: Rafael J. Wysocki
  Cc: Matthew Garrett, Len Brown, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu

On Sun, 27 Dec 2009 21:02:22 +0100
"Rafael J. Wysocki" <rjw@sisk.pl> wrote:

> From: Rafael J. Wysocki <rjw@sisk.pl>
> 
> Apparently, some machines may have problems with PCI run-time power
> management if MSIs are used for the hative PCIe PME signaling.  In
> particular, on the MSI Wind U-100 PCIe PME interrupts are not
> generated by a PCIe root port after a resume from suspend to RAM, if
> the system wake-up was triggered by a PME from the device attached to
> this port.  [It doesn't help to free the interrupt on suspend and
> request it back on resume, even if that is done along with disabling
> the MSI and re-enabling it, respectively.]  However, if INTx
> interrupts are used for this purpose on the same machine, everything
> works just fine.
> 
> For this reason, add a kernel command line switch allowing one to
> request that MSIs be not used for the native PCIe PME signaling,
> introduce a DMI table allowing us to blacklist machines that need
> this switch to be set by default and put the MSI Wind U-100 into this
> table.
> 
> Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
> ---

This scares me a little... seems like we're not restoring some state on
resume that's needed for MSIs to work?  Looks fine though.

-- 
Jesse Barnes, Intel Open Source Technology Center

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 4/12] PCI PM: Make it possible to force using INTx for PCIe PME signaling
  2009-12-27 20:02   ` Rafael J. Wysocki
  (?)
@ 2010-01-06 21:56   ` Jesse Barnes
  -1 siblings, 0 replies; 68+ messages in thread
From: Jesse Barnes @ 2010-01-06 21:56 UTC (permalink / raw)
  To: Rafael J. Wysocki
  Cc: Oliver, Linux PCI, LKML, ACPI Devel Maling List, Shaohua,
	Francois Romieu, pm list, Matthew Garrett

On Sun, 27 Dec 2009 21:02:22 +0100
"Rafael J. Wysocki" <rjw@sisk.pl> wrote:

> From: Rafael J. Wysocki <rjw@sisk.pl>
> 
> Apparently, some machines may have problems with PCI run-time power
> management if MSIs are used for the hative PCIe PME signaling.  In
> particular, on the MSI Wind U-100 PCIe PME interrupts are not
> generated by a PCIe root port after a resume from suspend to RAM, if
> the system wake-up was triggered by a PME from the device attached to
> this port.  [It doesn't help to free the interrupt on suspend and
> request it back on resume, even if that is done along with disabling
> the MSI and re-enabling it, respectively.]  However, if INTx
> interrupts are used for this purpose on the same machine, everything
> works just fine.
> 
> For this reason, add a kernel command line switch allowing one to
> request that MSIs be not used for the native PCIe PME signaling,
> introduce a DMI table allowing us to blacklist machines that need
> this switch to be set by default and put the MSI Wind U-100 into this
> table.
> 
> Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
> ---

This scares me a little... seems like we're not restoring some state on
resume that's needed for MSIs to work?  Looks fine though.

-- 
Jesse Barnes, Intel Open Source Technology Center

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 9/12] ACPI / PM: Introduce acpi_pm_wakeup_power()
  2009-12-27 20:06 ` [PATCH 9/12] ACPI / PM: Introduce acpi_pm_wakeup_power() Rafael J. Wysocki
@ 2010-01-06 22:00   ` Jesse Barnes
  2010-01-06 23:11     ` Rafael J. Wysocki
  2010-01-06 23:11     ` Rafael J. Wysocki
  2010-01-06 22:00   ` Jesse Barnes
  1 sibling, 2 replies; 68+ messages in thread
From: Jesse Barnes @ 2010-01-06 22:00 UTC (permalink / raw)
  To: Rafael J. Wysocki
  Cc: Matthew Garrett, Len Brown, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu

On Sun, 27 Dec 2009 21:06:26 +0100
"Rafael J. Wysocki" <rjw@sisk.pl> wrote:
>  /**
> + * acpi_pm_wakeup_power - Enable/disable device wake-up power.
> + * @dev: ACPI device to handle.
> + * @enable: Whether to enable or disable the wake-up power of the
> device.
> + */
> +int acpi_pm_wakeup_power(struct acpi_device *dev, bool enable)
> +{

I know we've got these all over now, but functions that just take a
bool are generally hard to read when you just look at the call site.
If it was called "acpi_pm_set_wakeup_power" and then took an on/off
enum it would be really easy to see, from the callsite, what was going
on.

It's a fairly minor complaint, but it's something that's always bugged
me about the PCI PM code in particular.

-- 
Jesse Barnes, Intel Open Source Technology Center

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 9/12] ACPI / PM: Introduce acpi_pm_wakeup_power()
  2009-12-27 20:06 ` [PATCH 9/12] ACPI / PM: Introduce acpi_pm_wakeup_power() Rafael J. Wysocki
  2010-01-06 22:00   ` Jesse Barnes
@ 2010-01-06 22:00   ` Jesse Barnes
  1 sibling, 0 replies; 68+ messages in thread
From: Jesse Barnes @ 2010-01-06 22:00 UTC (permalink / raw)
  To: Rafael J. Wysocki
  Cc: Oliver, Linux PCI, LKML, ACPI Devel Maling List, Shaohua,
	Francois Romieu, pm list, Matthew Garrett

On Sun, 27 Dec 2009 21:06:26 +0100
"Rafael J. Wysocki" <rjw@sisk.pl> wrote:
>  /**
> + * acpi_pm_wakeup_power - Enable/disable device wake-up power.
> + * @dev: ACPI device to handle.
> + * @enable: Whether to enable or disable the wake-up power of the
> device.
> + */
> +int acpi_pm_wakeup_power(struct acpi_device *dev, bool enable)
> +{

I know we've got these all over now, but functions that just take a
bool are generally hard to read when you just look at the call site.
If it was called "acpi_pm_set_wakeup_power" and then took an on/off
enum it would be really easy to see, from the callsite, what was going
on.

It's a fairly minor complaint, but it's something that's always bugged
me about the PCI PM code in particular.

-- 
Jesse Barnes, Intel Open Source Technology Center

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 4/12] PCI PM: Make it possible to force using INTx for PCIe PME signaling
  2010-01-06 21:56   ` Jesse Barnes
@ 2010-01-06 22:02     ` Matthew Garrett
  2010-01-06 23:00       ` Rafael J. Wysocki
  2010-01-06 23:00       ` Rafael J. Wysocki
  2010-01-06 22:02     ` Matthew Garrett
  1 sibling, 2 replies; 68+ messages in thread
From: Matthew Garrett @ 2010-01-06 22:02 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Rafael J. Wysocki, Len Brown, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu

On Wed, Jan 06, 2010 at 01:56:25PM -0800, Jesse Barnes wrote:

> This scares me a little... seems like we're not restoring some state on
> resume that's needed for MSIs to work?  Looks fine though.

Yeah, that was my concern. We debugged this a little on IRC and didn't 
get anywhere, though, so probably need to find someone who knows more 
about MSIs...

-- 
Matthew Garrett | mjg59@srcf.ucam.org

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 4/12] PCI PM: Make it possible to force using INTx for PCIe PME signaling
  2010-01-06 21:56   ` Jesse Barnes
  2010-01-06 22:02     ` Matthew Garrett
@ 2010-01-06 22:02     ` Matthew Garrett
  1 sibling, 0 replies; 68+ messages in thread
From: Matthew Garrett @ 2010-01-06 22:02 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Linux PCI, LKML, ACPI Devel Maling List, Francois Romieu, pm list

On Wed, Jan 06, 2010 at 01:56:25PM -0800, Jesse Barnes wrote:

> This scares me a little... seems like we're not restoring some state on
> resume that's needed for MSIs to work?  Looks fine though.

Yeah, that was my concern. We debugged this a little on IRC and didn't 
get anywhere, though, so probably need to find someone who knows more 
about MSIs...

-- 
Matthew Garrett | mjg59@srcf.ucam.org

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 10/12] PCI / ACPI / PM: Platform support for PCI PME wake-up (rev. 6)
  2009-12-27 20:07 ` [PATCH 10/12] PCI / ACPI / PM: Platform support for PCI PME wake-up (rev. 6) Rafael J. Wysocki
@ 2010-01-06 22:04   ` Jesse Barnes
  2010-01-07 21:18     ` Rafael J. Wysocki
  2010-01-07 21:18     ` Rafael J. Wysocki
  2010-01-06 22:04   ` Jesse Barnes
  1 sibling, 2 replies; 68+ messages in thread
From: Jesse Barnes @ 2010-01-06 22:04 UTC (permalink / raw)
  To: Rafael J. Wysocki
  Cc: Matthew Garrett, Len Brown, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu

On Sun, 27 Dec 2009 21:07:36 +0100
"Rafael J. Wysocki" <rjw@sisk.pl> wrote:

> From: Rafael J. Wysocki <rjw@sisk.pl>
> 
> Although the majority of PCI devices can generate PMEs that in
> principle may be used to wake up devices suspended at run time,
> platform support is generally necessary to convert PMEs into wake-up
> events that can be delivered to the kernel.  If ACPI is used for this
> purpose, a PME generated by a PCI device will trigger the ACPI GPE
> associated with the device to generate an ACPI wake-up event that we
> can set up a handler for, provided that everything is configured
> correctly.
> 
> Unfortunately, the subset of PCI devices that have GPEs associated
> with them is quite limited and the other devices have to rely on
> the GPEs associated with their upstream bridges and, possibly, the
> root bridge to generate ACPI wake-up events in response to PMEs from
> them.  Moreover, ACPI-based PCI hotplug also uses ACPI notify
> handlers that in general may conflict with the PM notify handlers,
> unless this issue is specifically taken care of.
> 
> Add ACPI platform support for PCI PME wake-up:
> o Add a framework making is possible to use ACPI system notify
>   handlers for both PM and hotplug at the same time and to take the
>   wake-up GPE sharing into account.
> o Add new PCI platform callback ->run_wake() to struct
>   pci_platform_pm_ops allowing us to enable/disable the platform to
>   generate wake-up events for given device.  Implemet this callback
>   for the ACPI platform.
> o Define ACPI wake-up handlers for PCI devices and PCI buses and make
>   the PCI-ACPI binding code register wake-up notifiers for devices
>   associated with wake-up GPEs.
> o Add function pci_dev_run_wake() which can be used by PCI drivers to
>   check if given device is capable of generating wake-up events at
>   run time.
> 
> Developed in cooperation with Matthew Garrett <mjg@redhat.com>.
> 
> Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>

Looks pretty good, though my comment about the bool taking functions
applies here too.

-- 
Jesse Barnes, Intel Open Source Technology Center

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 10/12] PCI / ACPI / PM: Platform support for PCI PME wake-up (rev. 6)
  2009-12-27 20:07 ` [PATCH 10/12] PCI / ACPI / PM: Platform support for PCI PME wake-up (rev. 6) Rafael J. Wysocki
  2010-01-06 22:04   ` Jesse Barnes
@ 2010-01-06 22:04   ` Jesse Barnes
  1 sibling, 0 replies; 68+ messages in thread
From: Jesse Barnes @ 2010-01-06 22:04 UTC (permalink / raw)
  To: Rafael J. Wysocki
  Cc: Oliver, Linux PCI, LKML, ACPI Devel Maling List, Shaohua,
	Francois Romieu, pm list, Matthew Garrett

On Sun, 27 Dec 2009 21:07:36 +0100
"Rafael J. Wysocki" <rjw@sisk.pl> wrote:

> From: Rafael J. Wysocki <rjw@sisk.pl>
> 
> Although the majority of PCI devices can generate PMEs that in
> principle may be used to wake up devices suspended at run time,
> platform support is generally necessary to convert PMEs into wake-up
> events that can be delivered to the kernel.  If ACPI is used for this
> purpose, a PME generated by a PCI device will trigger the ACPI GPE
> associated with the device to generate an ACPI wake-up event that we
> can set up a handler for, provided that everything is configured
> correctly.
> 
> Unfortunately, the subset of PCI devices that have GPEs associated
> with them is quite limited and the other devices have to rely on
> the GPEs associated with their upstream bridges and, possibly, the
> root bridge to generate ACPI wake-up events in response to PMEs from
> them.  Moreover, ACPI-based PCI hotplug also uses ACPI notify
> handlers that in general may conflict with the PM notify handlers,
> unless this issue is specifically taken care of.
> 
> Add ACPI platform support for PCI PME wake-up:
> o Add a framework making is possible to use ACPI system notify
>   handlers for both PM and hotplug at the same time and to take the
>   wake-up GPE sharing into account.
> o Add new PCI platform callback ->run_wake() to struct
>   pci_platform_pm_ops allowing us to enable/disable the platform to
>   generate wake-up events for given device.  Implemet this callback
>   for the ACPI platform.
> o Define ACPI wake-up handlers for PCI devices and PCI buses and make
>   the PCI-ACPI binding code register wake-up notifiers for devices
>   associated with wake-up GPEs.
> o Add function pci_dev_run_wake() which can be used by PCI drivers to
>   check if given device is capable of generating wake-up events at
>   run time.
> 
> Developed in cooperation with Matthew Garrett <mjg@redhat.com>.
> 
> Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>

Looks pretty good, though my comment about the bool taking functions
applies here too.

-- 
Jesse Barnes, Intel Open Source Technology Center

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 11/12] PCI PM: Run-time callbacks for PCI bus type (rev. 2)
  2009-12-27 20:08 ` Rafael J. Wysocki
  2010-01-06 22:06   ` Jesse Barnes
@ 2010-01-06 22:06   ` Jesse Barnes
  1 sibling, 0 replies; 68+ messages in thread
From: Jesse Barnes @ 2010-01-06 22:06 UTC (permalink / raw)
  To: Rafael J. Wysocki
  Cc: Matthew Garrett, Len Brown, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu

On Sun, 27 Dec 2009 21:08:24 +0100
"Rafael J. Wysocki" <rjw@sisk.pl> wrote:

> From: Rafael J. Wysocki <rjw@sisk.pl>
> 
> Introduce run-time PM callbacks for the PCI bus type.  Make the new
> callbacks work in analogy with the existing system sleep PM
> callbacks, so that the drivers already converted to struct dev_pm_ops
> can use their suspend and resume routines for run-time PM without
> modifications.
> 
> Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
> ---

Looks good.

-- 
Jesse Barnes, Intel Open Source Technology Center

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 11/12] PCI PM: Run-time callbacks for PCI bus type (rev. 2)
  2009-12-27 20:08 ` Rafael J. Wysocki
@ 2010-01-06 22:06   ` Jesse Barnes
  2010-01-06 22:06   ` Jesse Barnes
  1 sibling, 0 replies; 68+ messages in thread
From: Jesse Barnes @ 2010-01-06 22:06 UTC (permalink / raw)
  To: Rafael J. Wysocki
  Cc: Oliver, Linux PCI, LKML, ACPI Devel Maling List, Shaohua,
	Francois Romieu, pm list, Matthew Garrett

On Sun, 27 Dec 2009 21:08:24 +0100
"Rafael J. Wysocki" <rjw@sisk.pl> wrote:

> From: Rafael J. Wysocki <rjw@sisk.pl>
> 
> Introduce run-time PM callbacks for the PCI bus type.  Make the new
> callbacks work in analogy with the existing system sleep PM
> callbacks, so that the drivers already converted to struct dev_pm_ops
> can use their suspend and resume routines for run-time PM without
> modifications.
> 
> Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
> ---

Looks good.

-- 
Jesse Barnes, Intel Open Source Technology Center

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 4/12] PCI PM: Make it possible to force using INTx for PCIe PME signaling
  2010-01-06 22:02     ` Matthew Garrett
  2010-01-06 23:00       ` Rafael J. Wysocki
@ 2010-01-06 23:00       ` Rafael J. Wysocki
  2010-01-08 20:08         ` Len Brown
  2010-01-08 20:08         ` Len Brown
  1 sibling, 2 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2010-01-06 23:00 UTC (permalink / raw)
  To: Matthew Garrett
  Cc: Jesse Barnes, Len Brown, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu

On Wednesday 06 January 2010, Matthew Garrett wrote:
> On Wed, Jan 06, 2010 at 01:56:25PM -0800, Jesse Barnes wrote:
> 
> > This scares me a little... seems like we're not restoring some state on
> > resume that's needed for MSIs to work?  Looks fine though.
> 
> Yeah, that was my concern. We debugged this a little on IRC and didn't 
> get anywhere, though, so probably need to find someone who knows more 
> about MSIs...

I'd rather say someone who knows more about ICH7. :-)

MSIs work just fine after resume for the other devices in the same box,
so I'd say it's a chipset issue.

Rafael

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 4/12] PCI PM: Make it possible to force using INTx for PCIe PME signaling
  2010-01-06 22:02     ` Matthew Garrett
@ 2010-01-06 23:00       ` Rafael J. Wysocki
  2010-01-06 23:00       ` Rafael J. Wysocki
  1 sibling, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2010-01-06 23:00 UTC (permalink / raw)
  To: Matthew Garrett
  Cc: Linux PCI, LKML, Jesse Barnes, ACPI Devel Maling List,
	Francois Romieu, pm list

On Wednesday 06 January 2010, Matthew Garrett wrote:
> On Wed, Jan 06, 2010 at 01:56:25PM -0800, Jesse Barnes wrote:
> 
> > This scares me a little... seems like we're not restoring some state on
> > resume that's needed for MSIs to work?  Looks fine though.
> 
> Yeah, that was my concern. We debugged this a little on IRC and didn't 
> get anywhere, though, so probably need to find someone who knows more 
> about MSIs...

I'd rather say someone who knows more about ICH7. :-)

MSIs work just fine after resume for the other devices in the same box,
so I'd say it's a chipset issue.

Rafael

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 9/12] ACPI / PM: Introduce acpi_pm_wakeup_power()
  2010-01-06 22:00   ` Jesse Barnes
  2010-01-06 23:11     ` Rafael J. Wysocki
@ 2010-01-06 23:11     ` Rafael J. Wysocki
  2010-01-07 21:11       ` Rafael J. Wysocki
  2010-01-07 21:11       ` Rafael J. Wysocki
  1 sibling, 2 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2010-01-06 23:11 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Matthew Garrett, Len Brown, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu

On Wednesday 06 January 2010, Jesse Barnes wrote:
> On Sun, 27 Dec 2009 21:06:26 +0100
> "Rafael J. Wysocki" <rjw@sisk.pl> wrote:
> >  /**
> > + * acpi_pm_wakeup_power - Enable/disable device wake-up power.
> > + * @dev: ACPI device to handle.
> > + * @enable: Whether to enable or disable the wake-up power of the
> > device.
> > + */
> > +int acpi_pm_wakeup_power(struct acpi_device *dev, bool enable)
> > +{
> 
> I know we've got these all over now, but functions that just take a
> bool are generally hard to read when you just look at the call site.
> If it was called "acpi_pm_set_wakeup_power" and then took an on/off
> enum it would be really easy to see, from the callsite, what was going
> on.
> 
> It's a fairly minor complaint, but it's something that's always bugged
> me about the PCI PM code in particular.

Well, in this particular case acpi_pm_wakeup_power() uses a bool, because
acpi_pm_device_sleep_wake() (which is a caller of it) does.  IMO it won't
be logical to use something else just here.

Also, as you noticed above, this follows a convention used not only in the
PCI PM, but generally in the core PM code.  Although we could change this
convention, I'm not really sure that would be worth the effort.

Rafael

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 9/12] ACPI / PM: Introduce acpi_pm_wakeup_power()
  2010-01-06 22:00   ` Jesse Barnes
@ 2010-01-06 23:11     ` Rafael J. Wysocki
  2010-01-06 23:11     ` Rafael J. Wysocki
  1 sibling, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2010-01-06 23:11 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Linux PCI, LKML, ACPI Devel Maling List, Francois Romieu,
	pm list, Matthew Garrett

On Wednesday 06 January 2010, Jesse Barnes wrote:
> On Sun, 27 Dec 2009 21:06:26 +0100
> "Rafael J. Wysocki" <rjw@sisk.pl> wrote:
> >  /**
> > + * acpi_pm_wakeup_power - Enable/disable device wake-up power.
> > + * @dev: ACPI device to handle.
> > + * @enable: Whether to enable or disable the wake-up power of the
> > device.
> > + */
> > +int acpi_pm_wakeup_power(struct acpi_device *dev, bool enable)
> > +{
> 
> I know we've got these all over now, but functions that just take a
> bool are generally hard to read when you just look at the call site.
> If it was called "acpi_pm_set_wakeup_power" and then took an on/off
> enum it would be really easy to see, from the callsite, what was going
> on.
> 
> It's a fairly minor complaint, but it's something that's always bugged
> me about the PCI PM code in particular.

Well, in this particular case acpi_pm_wakeup_power() uses a bool, because
acpi_pm_device_sleep_wake() (which is a caller of it) does.  IMO it won't
be logical to use something else just here.

Also, as you noticed above, this follows a convention used not only in the
PCI PM, but generally in the core PM code.  Although we could change this
convention, I'm not really sure that would be worth the effort.

Rafael

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 9/12] ACPI / PM: Introduce acpi_pm_wakeup_power()
  2010-01-06 23:11     ` Rafael J. Wysocki
  2010-01-07 21:11       ` Rafael J. Wysocki
@ 2010-01-07 21:11       ` Rafael J. Wysocki
  1 sibling, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2010-01-07 21:11 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Matthew Garrett, Len Brown, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu

On Thursday 07 January 2010, Rafael J. Wysocki wrote:
> On Wednesday 06 January 2010, Jesse Barnes wrote:
> > On Sun, 27 Dec 2009 21:06:26 +0100
> > "Rafael J. Wysocki" <rjw@sisk.pl> wrote:
> > >  /**
> > > + * acpi_pm_wakeup_power - Enable/disable device wake-up power.
> > > + * @dev: ACPI device to handle.
> > > + * @enable: Whether to enable or disable the wake-up power of the
> > > device.
> > > + */
> > > +int acpi_pm_wakeup_power(struct acpi_device *dev, bool enable)
> > > +{
> > 
> > I know we've got these all over now, but functions that just take a
> > bool are generally hard to read when you just look at the call site.
> > If it was called "acpi_pm_set_wakeup_power" and then took an on/off
> > enum it would be really easy to see, from the callsite, what was going
> > on.
> > 
> > It's a fairly minor complaint, but it's something that's always bugged
> > me about the PCI PM code in particular.
> 
> Well, in this particular case acpi_pm_wakeup_power() uses a bool, because
> acpi_pm_device_sleep_wake() (which is a caller of it) does.  IMO it won't
> be logical to use something else just here.
> 
> Also, as you noticed above, this follows a convention used not only in the
> PCI PM, but generally in the core PM code.  Although we could change this
> convention, I'm not really sure that would be worth the effort.

That said, it looks like we can drop this (ie. [9/12]) patch altogether if the
next one is changed slightly.

I'll post the modified [10/12] shortly.

Rafael

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 9/12] ACPI / PM: Introduce acpi_pm_wakeup_power()
  2010-01-06 23:11     ` Rafael J. Wysocki
@ 2010-01-07 21:11       ` Rafael J. Wysocki
  2010-01-07 21:11       ` Rafael J. Wysocki
  1 sibling, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2010-01-07 21:11 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Linux PCI, LKML, ACPI Devel Maling List, Francois Romieu,
	pm list, Matthew Garrett

On Thursday 07 January 2010, Rafael J. Wysocki wrote:
> On Wednesday 06 January 2010, Jesse Barnes wrote:
> > On Sun, 27 Dec 2009 21:06:26 +0100
> > "Rafael J. Wysocki" <rjw@sisk.pl> wrote:
> > >  /**
> > > + * acpi_pm_wakeup_power - Enable/disable device wake-up power.
> > > + * @dev: ACPI device to handle.
> > > + * @enable: Whether to enable or disable the wake-up power of the
> > > device.
> > > + */
> > > +int acpi_pm_wakeup_power(struct acpi_device *dev, bool enable)
> > > +{
> > 
> > I know we've got these all over now, but functions that just take a
> > bool are generally hard to read when you just look at the call site.
> > If it was called "acpi_pm_set_wakeup_power" and then took an on/off
> > enum it would be really easy to see, from the callsite, what was going
> > on.
> > 
> > It's a fairly minor complaint, but it's something that's always bugged
> > me about the PCI PM code in particular.
> 
> Well, in this particular case acpi_pm_wakeup_power() uses a bool, because
> acpi_pm_device_sleep_wake() (which is a caller of it) does.  IMO it won't
> be logical to use something else just here.
> 
> Also, as you noticed above, this follows a convention used not only in the
> PCI PM, but generally in the core PM code.  Although we could change this
> convention, I'm not really sure that would be worth the effort.

That said, it looks like we can drop this (ie. [9/12]) patch altogether if the
next one is changed slightly.

I'll post the modified [10/12] shortly.

Rafael

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 10/12] PCI / ACPI / PM: Platform support for PCI PME wake-up (rev. 6)
  2010-01-06 22:04   ` Jesse Barnes
@ 2010-01-07 21:18     ` Rafael J. Wysocki
  2010-01-07 21:18     ` Rafael J. Wysocki
  1 sibling, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2010-01-07 21:18 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Matthew Garrett, Len Brown, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu

On Wednesday 06 January 2010, Jesse Barnes wrote:
> On Sun, 27 Dec 2009 21:07:36 +0100
> "Rafael J. Wysocki" <rjw@sisk.pl> wrote:
> 
> > From: Rafael J. Wysocki <rjw@sisk.pl>
> > 
> > Although the majority of PCI devices can generate PMEs that in
> > principle may be used to wake up devices suspended at run time,
> > platform support is generally necessary to convert PMEs into wake-up
> > events that can be delivered to the kernel.  If ACPI is used for this
> > purpose, a PME generated by a PCI device will trigger the ACPI GPE
> > associated with the device to generate an ACPI wake-up event that we
> > can set up a handler for, provided that everything is configured
> > correctly.
> > 
> > Unfortunately, the subset of PCI devices that have GPEs associated
> > with them is quite limited and the other devices have to rely on
> > the GPEs associated with their upstream bridges and, possibly, the
> > root bridge to generate ACPI wake-up events in response to PMEs from
> > them.  Moreover, ACPI-based PCI hotplug also uses ACPI notify
> > handlers that in general may conflict with the PM notify handlers,
> > unless this issue is specifically taken care of.
> > 
> > Add ACPI platform support for PCI PME wake-up:
> > o Add a framework making is possible to use ACPI system notify
> >   handlers for both PM and hotplug at the same time and to take the
> >   wake-up GPE sharing into account.
> > o Add new PCI platform callback ->run_wake() to struct
> >   pci_platform_pm_ops allowing us to enable/disable the platform to
> >   generate wake-up events for given device.  Implemet this callback
> >   for the ACPI platform.
> > o Define ACPI wake-up handlers for PCI devices and PCI buses and make
> >   the PCI-ACPI binding code register wake-up notifiers for devices
> >   associated with wake-up GPEs.
> > o Add function pci_dev_run_wake() which can be used by PCI drivers to
> >   check if given device is capable of generating wake-up events at
> >   run time.
> > 
> > Developed in cooperation with Matthew Garrett <mjg@redhat.com>.
> > 
> > Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
> 
> Looks pretty good, though my comment about the bool taking functions
> applies here too.

Hmm, I'm not sure if we can avoid that in this particular patch.

Since the .run_wake() callback in struct pci_platform_pm_ops takes the second
argument (the fact that it's a bool doesn't really matter here IMhO), we
need to propagate it all the way up to acpi_dev_run_wake_enable() (renamed
to acpi_dev_run_wake() in the patch below) in pci-acpi.c.

Anyway, as I said in the previous message, patch [9/12] can be dropped if the
$subject one is modified slightly, so the updated [10/12] is appended below.

Rafael

---
From: Rafael J. Wysocki <rjw@sisk.pl>
Subject: PCI / ACPI / PM: Platform support for PCI PME wake-up (rev. 7)

Although the majority of PCI devices can generate PMEs that in
principle may be used to wake up devices suspended at run time,
platform support is generally necessary to convert PMEs into wake-up
events that can be delivered to the kernel.  If ACPI is used for this
purpose, a PME generated by a PCI device will trigger the ACPI GPE
associated with the device to generate an ACPI wake-up event that we
can set up a handler for, provided that everything is configured
correctly.

Unfortunately, the subset of PCI devices that have GPEs associated
with them is quite limited and the other devices have to rely on
the GPEs associated with their upstream bridges and, possibly, the
root bridge to generate ACPI wake-up events in response to PMEs from
them.  Moreover, ACPI-based PCI hotplug also uses ACPI notify
handlers that in general may conflict with the PM notify handlers,
unless this issue is specifically taken care of.

Add ACPI platform support for PCI PME wake-up:
o Add a framework making is possible to use ACPI system notify
  handlers for both PM and hotplug at the same time and to take the
  wake-up GPE sharing into account.
o Add new PCI platform callback ->run_wake() to struct
  pci_platform_pm_ops allowing us to enable/disable the platform to
  generate wake-up events for given device.  Implemet this callback
  for the ACPI platform.
o Define ACPI wake-up handlers for PCI devices and PCI buses and make
  the PCI-ACPI binding code register wake-up notifiers for devices
  associated with wake-up GPEs.
o Add function pci_dev_run_wake() which can be used by PCI drivers to
  check if given device is capable of generating wake-up events at
  run time.

Developed in cooperation with Matthew Garrett <mjg@redhat.com>.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
 drivers/acpi/internal.h            |    2 
 drivers/acpi/pci_bind.c            |   14 +
 drivers/acpi/pci_root.c            |    8 
 drivers/pci/hotplug/acpiphp_glue.c |   23 --
 drivers/pci/pci-acpi.c             |  341 +++++++++++++++++++++++++++++++++++++
 drivers/pci/pci.c                  |   67 +++++++
 drivers/pci/pci.h                  |    7 
 include/acpi/acpi_bus.h            |    4 
 include/linux/pci-acpi.h           |   10 +
 include/linux/pci.h                |    1 
 10 files changed, 460 insertions(+), 17 deletions(-)

Index: linux-2.6/drivers/pci/pci.h
===================================================================
--- linux-2.6.orig/drivers/pci/pci.h
+++ linux-2.6/drivers/pci/pci.h
@@ -35,6 +35,10 @@ int pci_probe_reset_function(struct pci_
  *
  * @sleep_wake: enables/disables the system wake up capability of given device
  *
+ * @run_wake: enables/disables the platform to generate run-time wake-up events
+ *		for given device (the device's wake-up capability has to be
+ *		enabled by @sleep_wake for this feature to work)
+ *
  * If given platform is generally capable of power managing PCI devices, all of
  * these callbacks are mandatory.
  */
@@ -44,12 +48,15 @@ struct pci_platform_pm_ops {
 	pci_power_t (*choose_state)(struct pci_dev *dev);
 	bool (*can_wakeup)(struct pci_dev *dev);
 	int (*sleep_wake)(struct pci_dev *dev, bool enable);
+	int (*run_wake)(struct pci_dev *dev, bool enable);
 };
 
 extern int pci_set_platform_pm(struct pci_platform_pm_ops *ops);
 extern void pci_update_current_state(struct pci_dev *dev, pci_power_t state);
 extern void pci_disable_enabled_device(struct pci_dev *dev);
 extern bool pci_check_pme_status(struct pci_dev *dev);
+extern int __pci_pme_wakeup(struct pci_dev *dev, void *ign);
+extern void pci_pme_wakeup_bus(struct pci_bus *bus);
 extern void pci_pm_init(struct pci_dev *dev);
 extern void platform_pci_wakeup_init(struct pci_dev *dev);
 extern void pci_allocate_cap_save_buffers(struct pci_dev *dev);
Index: linux-2.6/drivers/pci/pci-acpi.c
===================================================================
--- linux-2.6.orig/drivers/pci/pci-acpi.c
+++ linux-2.6/drivers/pci/pci-acpi.c
@@ -16,8 +16,276 @@
 #include <acpi/acpi_bus.h>
 
 #include <linux/pci-acpi.h>
+#include <linux/pm_runtime.h>
 #include "pci.h"
 
+static DEFINE_MUTEX(pci_acpi_notifier_mtx);
+
+struct pci_acpi_notify_data
+{
+	acpi_notify_handler hp_cb;
+	void *hp_data;
+	struct pci_bus *pci_bus;
+	struct pci_dev *pci_dev;
+};
+
+/**
+ * pci_acpi_event_fn - Universal system notification handler.
+ * @handle: ACPI handle of a device the notification is for.
+ * @event: Type of the signaled event.
+ * @ign: The value of this argument is ignored.
+ *
+ * Use @handle to obtain the address of the ACPI device object the event is
+ * signaled for.  If this is a wake-up event, execute the appropriate PME
+ * handler for the bus or device represented by it (or both, if @dev is a
+ * bridge).  If this is not a wake-up event, execute the hotplug notify handler
+ * for @handle.
+ */
+static void pci_acpi_event_fn(acpi_handle handle, u32 event, void *ign)
+{
+	struct acpi_device *dev;
+	struct pci_acpi_notify_data *nd;
+
+	if (ACPI_FAILURE(acpi_bus_get_device(handle, &dev))) {
+		pr_warning("ACPI handle has no context in %s!\n", __func__);
+		return;
+	}
+
+	mutex_lock(&pci_acpi_notifier_mtx);
+
+	nd = dev->bus_data;
+	if (!nd)
+		goto out;
+
+	if (event == ACPI_NOTIFY_DEVICE_WAKE) {
+		if (nd->pci_bus) {
+			pci_pme_wakeup_bus(nd->pci_bus);
+		}
+		if (nd->pci_dev) {
+			pci_check_pme_status(nd->pci_dev);
+			pm_request_resume(&nd->pci_dev->dev);
+		}
+	} else if (nd->hp_cb) {
+		nd->hp_cb(handle, event, nd->hp_data);
+	}
+
+ out:
+	mutex_unlock(&pci_acpi_notifier_mtx);
+}
+
+/**
+ * add_notify_data - Create a new notify data object for given ACPI device.
+ * @dev: Device to create the notify data object for.
+ */
+static struct pci_acpi_notify_data *add_notify_data(struct acpi_device *dev)
+{
+	struct pci_acpi_notify_data *nd;
+
+	nd = kzalloc(sizeof(*nd), GFP_KERNEL);
+	if (!nd)
+		return NULL;
+
+	dev->bus_data = nd;
+	return nd;
+}
+
+/**
+ * remove_notify_data - Remove the notify data object from given ACPI device.
+ * @dev: Device to remove the notify data object from.
+ */
+static void remove_notify_data(struct acpi_device *dev)
+{
+	kfree(dev->bus_data);
+	dev->bus_data = NULL;
+}
+
+/**
+ * pci_acpi_add_hp_notifier - Register a hotplug notifier for given device.
+ * @handle: ACPI handle of the device to register the notifier for.
+ * @handler: Callback to execute for hotplug events related to @handle.
+ * @context: Pointer to the context data to pass to @handler.
+ *
+ * Use @handle to get an ACPI device object and check if there is a notify data
+ * object for it.  If this is the case, add @handler and @context to the
+ * existing notify data object, unless there already is a hotplug handler in
+ * there.  Otherwise, create a new notify data object for the ACPI device
+ * associated with @handle and add @handler and @context to it.
+ */
+acpi_status pci_acpi_add_hp_notifier(acpi_handle handle,
+				     acpi_notify_handler handler, void *context)
+{
+	struct pci_acpi_notify_data *nd;
+	struct acpi_device *dev;
+	acpi_status status = AE_OK;
+
+	if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &dev)))
+		return AE_NOT_FOUND;
+
+	mutex_lock(&pci_acpi_notifier_mtx);
+
+	nd = dev->bus_data;
+	if (nd) {
+		if (!nd->hp_cb)
+			goto add;
+
+		status = AE_ALREADY_EXISTS;
+		goto out;
+	}
+
+	nd = add_notify_data(dev);
+	if (!nd) {
+		status = AE_NO_MEMORY;
+		goto out;
+	}
+
+	status = acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
+						pci_acpi_event_fn, NULL);
+	if (ACPI_FAILURE(status)) {
+		remove_notify_data(dev);
+		goto out;
+	}
+
+ add:
+	nd->hp_cb = handler;
+	nd->hp_data = context;
+
+ out:
+	mutex_unlock(&pci_acpi_notifier_mtx);
+
+	return status;
+}
+EXPORT_SYMBOL_GPL(pci_acpi_add_hp_notifier);
+
+/**
+ * pci_acpi_remove_hp_notifier - Unregister a hotplug notifier for given device.
+ * @handle: ACPI handle of the device to unregister the notifier for.
+ * @handler: Callback executed for hotplug events related to @handle.
+ *
+ * Remove the hotplug callback and the pointer to the hotplug context data from
+ * the notify data object belonging to the device represented by @handle.  If
+ * the notify data object is not necessary any more, remove it altogether.
+ */
+acpi_status pci_acpi_remove_hp_notifier(acpi_handle handle,
+					acpi_notify_handler handler)
+{
+	struct pci_acpi_notify_data *nd;
+	struct acpi_device *dev;
+	acpi_status status = AE_NOT_FOUND;
+
+	if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &dev)))
+		return AE_NOT_FOUND;
+
+	mutex_lock(&pci_acpi_notifier_mtx);
+
+	nd = dev->bus_data;
+	if (!nd)
+		goto out;
+
+	nd->hp_data = NULL;
+	nd->hp_cb = NULL;
+
+	if (nd->pci_bus || nd->pci_dev)
+		goto out;
+
+	status = acpi_remove_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
+						pci_acpi_event_fn);
+	remove_notify_data(dev);
+
+ out:
+	mutex_unlock(&pci_acpi_notifier_mtx);
+	return status;
+}
+EXPORT_SYMBOL_GPL(pci_acpi_remove_hp_notifier);
+
+/**
+ * pci_acpi_add_pm_notifier - Register PM notifier for given device.
+ * @dev: ACPI device to add the notifier for.
+ * @pci_dev: PCI device to check for the PME status if an event is signaled.
+ * @pci_bus: PCI bus to walk (checking PME status) if an event is signaled.
+ *
+ * Check if there is a notify data object for @dev and if that is the case, add
+ * @pci_dev to it as the device whose PME status should be checked whenever a PM
+ * event is signaled for @dev.  Also, add @pci_bus to it as the bus to walk
+ * checking the PME status of all devices on it whenever a PM event is signaled
+ * for @dev.
+ *
+ * Otherwise, create a new notify data object for @dev and add both @pci_dev and
+ * @pci_bus to it.
+ *
+ * NOTE: @dev need not be a run-wake or wake-up device to be a valid source of
+ * PM wake-up events.  For example, wake-up events may be generated for bridges
+ * if one of the devices below the bridge is signaling PME, even if the bridge
+ * itself doesn't have a wake-up GPE associated with it.
+ */
+acpi_status pci_acpi_add_pm_notifier(struct acpi_device *dev,
+				     struct pci_dev *pci_dev,
+				     struct pci_bus *pci_bus)
+{
+	struct pci_acpi_notify_data *nd;
+	acpi_status status = AE_OK;
+
+	mutex_lock(&pci_acpi_notifier_mtx);
+
+	nd = dev->bus_data;
+	if (nd)
+		goto add;
+
+	nd = add_notify_data(dev);
+	if (!nd) {
+		status = AE_NO_MEMORY;
+		goto out;
+	}
+
+	status = acpi_install_notify_handler(dev->handle, ACPI_SYSTEM_NOTIFY,
+						pci_acpi_event_fn, NULL);
+	if (ACPI_FAILURE(status)) {
+		remove_notify_data(dev);
+		goto out;
+	}
+
+ add:
+	nd->pci_dev = pci_dev;
+	nd->pci_bus = pci_bus;
+
+ out:
+	mutex_unlock(&pci_acpi_notifier_mtx);
+	return status;
+}
+
+/**
+ * pci_acpi_remove_pm_notifier - Unregister PM notifier for given device.
+ * @dev: ACPI device to remove the notifier from.
+ *
+ * Find the notify data object for @dev and clear its @pci_dev and @pci_bus
+ * fields.  If the notify data object is not necessary any more after that,
+ * remove it too.
+ */
+acpi_status pci_acpi_remove_pm_notifier(struct acpi_device *dev)
+{
+	struct pci_acpi_notify_data *nd;
+	acpi_status status = AE_NOT_FOUND;
+
+	mutex_lock(&pci_acpi_notifier_mtx);
+
+	nd = dev->bus_data;
+	if (!nd)
+		goto out;
+
+	nd->pci_dev = NULL;
+	nd->pci_bus = NULL;
+
+	if (nd->hp_cb)
+		goto out;
+
+	status = acpi_remove_notify_handler(dev->handle, ACPI_SYSTEM_NOTIFY,
+						pci_acpi_event_fn);
+	remove_notify_data(dev);
+
+ out:
+	mutex_unlock(&pci_acpi_notifier_mtx);
+	return status;
+}
+
 /*
  * _SxD returns the D-state with the highest power
  * (lowest D-state number) supported in the S-state "x".
@@ -131,12 +399,85 @@ static int acpi_pci_sleep_wake(struct pc
 	return 0;
 }
 
+/**
+ * acpi_dev_run_wake - Enable/disable wake-up for given device.
+ * @phys_dev: Device to enable/disable the platform to wake-up the system for.
+ * @enable: Whether enable or disable the wake-up functionality.
+ *
+ * Find the ACPI device object corresponding to @pci_dev and try to
+ * enable/disable the GPE associated with it.
+ */
+static int acpi_dev_run_wake(struct device *phys_dev, bool enable)
+{
+	struct acpi_device *dev;
+	acpi_handle handle;
+	int error = -ENODEV;
+
+	if (!device_run_wake(phys_dev))
+		return -EINVAL;
+
+	handle = DEVICE_ACPI_HANDLE(phys_dev);
+	if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &dev))) {
+		dev_dbg(phys_dev, "ACPI handle has no context in %s!\n",
+			__func__);
+		return -ENODEV;
+	}
+
+	if (enable) {
+		if (!dev->wakeup.run_wake_count++) {
+			acpi_enable_wakeup_device_power(dev, ACPI_STATE_S0);
+			acpi_ref_runtime_gpe(dev->wakeup.gpe_device,
+						dev->wakeup.gpe_number);
+		}
+	} else if (dev->wakeup.run_wake_count > 0) {
+		if (!--dev->wakeup.run_wake_count) {
+			acpi_unref_runtime_gpe(dev->wakeup.gpe_device,
+						dev->wakeup.gpe_number);
+			acpi_disable_wakeup_device_power(dev);
+		}
+	} else {
+		error = -EALREADY;
+	}
+
+	return error;
+}
+
+static void acpi_pci_propagate_run_wake(struct pci_bus *bus, bool enable)
+{
+	while (bus->parent) {
+		struct pci_dev *bridge = bus->self;
+
+		if (bridge->pme_interrupt)
+			return;
+		if (!acpi_dev_run_wake(&bridge->dev, enable))
+			return;
+		bus = bus->parent;
+	}
+
+	/* We have reached the root bus. */
+	if (bus->bridge)
+		acpi_dev_run_wake(bus->bridge, enable);
+}
+
+static int acpi_pci_run_wake(struct pci_dev *dev, bool enable)
+{
+	if (dev->pme_interrupt)
+		return 0;
+
+	if (!acpi_dev_run_wake(&dev->dev, enable))
+		return 0;
+
+	acpi_pci_propagate_run_wake(dev->bus, enable);
+	return 0;
+}
+
 static struct pci_platform_pm_ops acpi_pci_platform_pm = {
 	.is_manageable = acpi_pci_power_manageable,
 	.set_state = acpi_pci_set_power_state,
 	.choose_state = acpi_pci_choose_state,
 	.can_wakeup = acpi_pci_can_wakeup,
 	.sleep_wake = acpi_pci_sleep_wake,
+	.run_wake = acpi_pci_run_wake,
 };
 
 /* ACPI bus type */
Index: linux-2.6/drivers/pci/pci.c
===================================================================
--- linux-2.6.orig/drivers/pci/pci.c
+++ linux-2.6/drivers/pci/pci.c
@@ -20,6 +20,7 @@
 #include <linux/pm_wakeup.h>
 #include <linux/interrupt.h>
 #include <linux/device.h>
+#include <linux/pm_runtime.h>
 #include <asm/setup.h>
 #include "pci.h"
 
@@ -462,6 +463,12 @@ static inline int platform_pci_sleep_wak
 			pci_platform_pm->sleep_wake(dev, enable) : -ENODEV;
 }
 
+static inline int platform_pci_run_wake(struct pci_dev *dev, bool enable)
+{
+	return pci_platform_pm ?
+			pci_platform_pm->run_wake(dev, enable) : -ENODEV;
+}
+
 /**
  * pci_raw_set_power_state - Use PCI PM registers to set the power state of
  *                           given PCI device
@@ -1230,6 +1237,31 @@ bool pci_check_pme_status(struct pci_dev
 }
 
 /**
+ * pci_pme_wakeup - Wake up a PCI device if its PME Status bit is set.
+ * @dev: Device to handle.
+ * @ign: Ignored.
+ *
+ * Check if @dev has generated PME and queue a resume request for it in that
+ * case.
+ */
+static int pci_pme_wakeup(struct pci_dev *dev, void *ign)
+{
+	if (pci_check_pme_status(dev))
+		pm_request_resume(&dev->dev);
+	return 0;
+}
+
+/**
+ * pci_pme_wakeup_bus - Walk given bus and wake up devices on it, if necessary.
+ * @bus: Top bus of the subtree to walk.
+ */
+void pci_pme_wakeup_bus(struct pci_bus *bus)
+{
+	if (bus)
+		pci_walk_bus(bus, pci_pme_wakeup, NULL);
+}
+
+/**
  * pci_pme_capable - check the capability of PCI device to generate PME#
  * @dev: PCI device to handle.
  * @state: PCI state from which device will issue PME#.
@@ -1434,6 +1466,41 @@ int pci_back_from_sleep(struct pci_dev *
 }
 
 /**
+ * pci_dev_run_wake - Check if device can generate run-time wake-up events.
+ * @dev: Device to check.
+ *
+ * Return true if the device itself is cabable of generating wake-up events
+ * (through the platform or using the native PCIe PME) or if the device supports
+ * PME and one of its upstream bridges can generate wake-up events.
+ */
+bool pci_dev_run_wake(struct pci_dev *dev)
+{
+	struct pci_bus *bus = dev->bus;
+
+	if (device_run_wake(&dev->dev))
+		return true;
+
+	if (!dev->pme_support)
+		return false;
+
+	while (bus->parent) {
+		struct pci_dev *bridge = bus->self;
+
+		if (device_run_wake(&bridge->dev))
+			return true;
+
+		bus = bus->parent;
+	}
+
+	/* We have reached the root bus. */
+	if (bus->bridge)
+		return device_run_wake(bus->bridge);
+
+	return false;
+}
+EXPORT_SYMBOL_GPL(pci_dev_run_wake);
+
+/**
  * pci_pm_init - Initialize PM functions of given PCI device
  * @dev: PCI device to handle.
  */
Index: linux-2.6/include/linux/pci-acpi.h
===================================================================
--- linux-2.6.orig/include/linux/pci-acpi.h
+++ linux-2.6/include/linux/pci-acpi.h
@@ -11,6 +11,16 @@
 #include <linux/acpi.h>
 
 #ifdef CONFIG_ACPI
+extern acpi_status pci_acpi_add_hp_notifier(acpi_handle handle,
+					     acpi_notify_handler handler,
+					     void *context);
+extern acpi_status pci_acpi_remove_hp_notifier(acpi_handle handle,
+					       acpi_notify_handler handler);
+extern acpi_status pci_acpi_add_pm_notifier(struct acpi_device *dev,
+					     struct pci_dev *pci_dev,
+					     struct pci_bus *pci_bus);
+extern acpi_status pci_acpi_remove_pm_notifier(struct acpi_device *dev);
+
 static inline acpi_handle acpi_find_root_bridge_handle(struct pci_dev *pdev)
 {
 	struct pci_bus *pbus = pdev->bus;
Index: linux-2.6/drivers/acpi/pci_bind.c
===================================================================
--- linux-2.6.orig/drivers/acpi/pci_bind.c
+++ linux-2.6/drivers/acpi/pci_bind.c
@@ -26,7 +26,9 @@
 #include <linux/kernel.h>
 #include <linux/types.h>
 #include <linux/pci.h>
+#include <linux/pci-acpi.h>
 #include <linux/acpi.h>
+#include <linux/pm_runtime.h>
 #include <acpi/acpi_bus.h>
 #include <acpi/acpi_drivers.h>
 
@@ -38,7 +40,13 @@ static int acpi_pci_unbind(struct acpi_d
 	struct pci_dev *dev;
 
 	dev = acpi_get_pci_dev(device->handle);
-	if (!dev || !dev->subordinate)
+	if (!dev)
+		goto out;
+
+	device_set_run_wake(&dev->dev, false);
+	pci_acpi_remove_pm_notifier(device);
+
+	if (!dev->subordinate)
 		goto out;
 
 	acpi_pci_irq_del_prt(dev->subordinate);
@@ -62,6 +70,10 @@ static int acpi_pci_bind(struct acpi_dev
 	if (!dev)
 		return 0;
 
+	pci_acpi_add_pm_notifier(device, dev, dev->subordinate);
+	if (device->wakeup.flags.run_wake)
+		device_set_run_wake(&dev->dev, true);
+
 	/*
 	 * Install the 'bind' function to facilitate callbacks for
 	 * children of the P2P bridge.
Index: linux-2.6/drivers/pci/hotplug/acpiphp_glue.c
===================================================================
--- linux-2.6.orig/drivers/pci/hotplug/acpiphp_glue.c
+++ linux-2.6/drivers/pci/hotplug/acpiphp_glue.c
@@ -236,8 +236,7 @@ register_slot(acpi_handle handle, u32 lv
 
 	/* install notify handler */
 	if (!(newfunc->flags & FUNC_HAS_DCK)) {
-		status = acpi_install_notify_handler(handle,
-					     ACPI_SYSTEM_NOTIFY,
+		status = pci_acpi_add_hp_notifier(handle,
 					     handle_hotplug_event_func,
 					     newfunc);
 
@@ -288,14 +287,12 @@ static void init_bridge_misc(struct acpi
 	/* install notify handler */
 	if (bridge->type != BRIDGE_TYPE_HOST) {
 		if ((bridge->flags & BRIDGE_HAS_EJ0) && bridge->func) {
-			status = acpi_remove_notify_handler(bridge->func->handle,
-						ACPI_SYSTEM_NOTIFY,
+			status = pci_acpi_remove_hp_notifier(bridge->func->handle,
 						handle_hotplug_event_func);
 			if (ACPI_FAILURE(status))
 				err("failed to remove notify handler\n");
 		}
-		status = acpi_install_notify_handler(bridge->handle,
-					     ACPI_SYSTEM_NOTIFY,
+		status = pci_acpi_add_hp_notifier(bridge->handle,
 					     handle_hotplug_event_bridge,
 					     bridge);
 
@@ -505,15 +502,14 @@ static void cleanup_bridge(struct acpiph
 	acpi_status status;
 	acpi_handle handle = bridge->handle;
 
-	status = acpi_remove_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
+	status = pci_acpi_remove_hp_notifier(handle,
 					    handle_hotplug_event_bridge);
 	if (ACPI_FAILURE(status))
 		err("failed to remove notify handler\n");
 
 	if ((bridge->type != BRIDGE_TYPE_HOST) &&
 	    ((bridge->flags & BRIDGE_HAS_EJ0) && bridge->func)) {
-		status = acpi_install_notify_handler(bridge->func->handle,
-						ACPI_SYSTEM_NOTIFY,
+		status = pci_acpi_add_hp_notifier(bridge->func->handle,
 						handle_hotplug_event_func,
 						bridge->func);
 		if (ACPI_FAILURE(status))
@@ -529,8 +525,7 @@ static void cleanup_bridge(struct acpiph
 				unregister_dock_notifier(&func->nb);
 			}
 			if (!(func->flags & FUNC_HAS_DCK)) {
-				status = acpi_remove_notify_handler(func->handle,
-						ACPI_SYSTEM_NOTIFY,
+				status = pci_acpi_remove_hp_notifier(func->handle,
 						handle_hotplug_event_func);
 				if (ACPI_FAILURE(status))
 					err("failed to remove notify handler\n");
@@ -592,7 +587,7 @@ static void remove_bridge(acpi_handle ha
 	if (bridge)
 		cleanup_bridge(bridge);
 	else
-		acpi_remove_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
+		pci_acpi_remove_hp_notifier(handle,
 					   handle_hotplug_event_bridge);
 }
 
@@ -1278,8 +1273,8 @@ find_root_bridges(acpi_handle handle, u3
 	int *count = (int *)context;
 
 	if (acpi_is_root_bridge(handle)) {
-		acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
-				handle_hotplug_event_bridge, NULL);
+		pci_acpi_add_hp_notifier(handle,
+					handle_hotplug_event_bridge, NULL);
 			(*count)++;
 	}
 	return AE_OK ;
Index: linux-2.6/include/linux/pci.h
===================================================================
--- linux-2.6.orig/include/linux/pci.h
+++ linux-2.6/include/linux/pci.h
@@ -756,6 +756,7 @@ int pci_wake_from_d3(struct pci_dev *dev
 pci_power_t pci_target_state(struct pci_dev *dev);
 int pci_prepare_to_sleep(struct pci_dev *dev);
 int pci_back_from_sleep(struct pci_dev *dev);
+bool pci_dev_run_wake(struct pci_dev *dev);
 
 /* Functions for PCI Hotplug drivers to use */
 int pci_bus_find_capability(struct pci_bus *bus, unsigned int devfn, int cap);
Index: linux-2.6/include/acpi/acpi_bus.h
===================================================================
--- linux-2.6.orig/include/acpi/acpi_bus.h
+++ linux-2.6/include/acpi/acpi_bus.h
@@ -282,6 +282,7 @@ struct acpi_device {
 	struct device dev;
 	struct acpi_bus_ops bus_ops;	/* workaround for different code path for hotplug */
 	enum acpi_bus_removal_type removal_type;	/* indicate for different removal type */
+	void *bus_data;
 };
 
 static inline void *acpi_driver_data(struct acpi_device *d)
@@ -388,6 +389,9 @@ acpi_handle acpi_get_pci_rootbridge_hand
 struct acpi_pci_root *acpi_pci_find_root(acpi_handle handle);
 #define DEVICE_ACPI_HANDLE(dev) ((acpi_handle)((dev)->archdata.acpi_handle))
 
+int acpi_enable_wakeup_device_power(struct acpi_device *dev, int state);
+int acpi_disable_wakeup_device_power(struct acpi_device *dev);
+
 #ifdef CONFIG_PM_SLEEP
 int acpi_pm_device_sleep_state(struct device *, int *);
 int acpi_pm_device_sleep_wake(struct device *, bool);
Index: linux-2.6/drivers/acpi/pci_root.c
===================================================================
--- linux-2.6.orig/drivers/acpi/pci_root.c
+++ linux-2.6/drivers/acpi/pci_root.c
@@ -30,6 +30,7 @@
 #include <linux/proc_fs.h>
 #include <linux/spinlock.h>
 #include <linux/pm.h>
+#include <linux/pm_runtime.h>
 #include <linux/pci.h>
 #include <linux/pci-acpi.h>
 #include <linux/acpi.h>
@@ -528,6 +529,10 @@ static int __devinit acpi_pci_root_add(s
 	if (flags != base_flags)
 		acpi_pci_osc_support(root, flags);
 
+	pci_acpi_add_pm_notifier(device, NULL, root->bus);
+	if (device->wakeup.flags.run_wake)
+		device_set_run_wake(root->bus->bridge, true);
+
 	return 0;
 
 end:
@@ -549,6 +554,9 @@ static int acpi_pci_root_remove(struct a
 {
 	struct acpi_pci_root *root = acpi_driver_data(device);
 
+	device_set_run_wake(root->bus->bridge, false);
+	pci_acpi_remove_pm_notifier(device);
+
 	kfree(root);
 	return 0;
 }
Index: linux-2.6/drivers/acpi/internal.h
===================================================================
--- linux-2.6.orig/drivers/acpi/internal.h
+++ linux-2.6/drivers/acpi/internal.h
@@ -36,8 +36,6 @@ static inline int acpi_debug_init(void) 
 int acpi_power_init(void);
 int acpi_device_sleep_wake(struct acpi_device *dev,
                            int enable, int sleep_state, int dev_state);
-int acpi_enable_wakeup_device_power(struct acpi_device *dev, int sleep_state);
-int acpi_disable_wakeup_device_power(struct acpi_device *dev);
 int acpi_power_get_inferred_state(struct acpi_device *device);
 int acpi_power_transition(struct acpi_device *device, int state);
 extern int acpi_power_nocheck;

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 10/12] PCI / ACPI / PM: Platform support for PCI PME wake-up (rev. 6)
  2010-01-06 22:04   ` Jesse Barnes
  2010-01-07 21:18     ` Rafael J. Wysocki
@ 2010-01-07 21:18     ` Rafael J. Wysocki
  1 sibling, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2010-01-07 21:18 UTC (permalink / raw)
  To: Jesse Barnes
  Cc: Linux PCI, LKML, ACPI Devel Maling List, Francois Romieu,
	pm list, Matthew Garrett

On Wednesday 06 January 2010, Jesse Barnes wrote:
> On Sun, 27 Dec 2009 21:07:36 +0100
> "Rafael J. Wysocki" <rjw@sisk.pl> wrote:
> 
> > From: Rafael J. Wysocki <rjw@sisk.pl>
> > 
> > Although the majority of PCI devices can generate PMEs that in
> > principle may be used to wake up devices suspended at run time,
> > platform support is generally necessary to convert PMEs into wake-up
> > events that can be delivered to the kernel.  If ACPI is used for this
> > purpose, a PME generated by a PCI device will trigger the ACPI GPE
> > associated with the device to generate an ACPI wake-up event that we
> > can set up a handler for, provided that everything is configured
> > correctly.
> > 
> > Unfortunately, the subset of PCI devices that have GPEs associated
> > with them is quite limited and the other devices have to rely on
> > the GPEs associated with their upstream bridges and, possibly, the
> > root bridge to generate ACPI wake-up events in response to PMEs from
> > them.  Moreover, ACPI-based PCI hotplug also uses ACPI notify
> > handlers that in general may conflict with the PM notify handlers,
> > unless this issue is specifically taken care of.
> > 
> > Add ACPI platform support for PCI PME wake-up:
> > o Add a framework making is possible to use ACPI system notify
> >   handlers for both PM and hotplug at the same time and to take the
> >   wake-up GPE sharing into account.
> > o Add new PCI platform callback ->run_wake() to struct
> >   pci_platform_pm_ops allowing us to enable/disable the platform to
> >   generate wake-up events for given device.  Implemet this callback
> >   for the ACPI platform.
> > o Define ACPI wake-up handlers for PCI devices and PCI buses and make
> >   the PCI-ACPI binding code register wake-up notifiers for devices
> >   associated with wake-up GPEs.
> > o Add function pci_dev_run_wake() which can be used by PCI drivers to
> >   check if given device is capable of generating wake-up events at
> >   run time.
> > 
> > Developed in cooperation with Matthew Garrett <mjg@redhat.com>.
> > 
> > Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
> 
> Looks pretty good, though my comment about the bool taking functions
> applies here too.

Hmm, I'm not sure if we can avoid that in this particular patch.

Since the .run_wake() callback in struct pci_platform_pm_ops takes the second
argument (the fact that it's a bool doesn't really matter here IMhO), we
need to propagate it all the way up to acpi_dev_run_wake_enable() (renamed
to acpi_dev_run_wake() in the patch below) in pci-acpi.c.

Anyway, as I said in the previous message, patch [9/12] can be dropped if the
$subject one is modified slightly, so the updated [10/12] is appended below.

Rafael

---
From: Rafael J. Wysocki <rjw@sisk.pl>
Subject: PCI / ACPI / PM: Platform support for PCI PME wake-up (rev. 7)

Although the majority of PCI devices can generate PMEs that in
principle may be used to wake up devices suspended at run time,
platform support is generally necessary to convert PMEs into wake-up
events that can be delivered to the kernel.  If ACPI is used for this
purpose, a PME generated by a PCI device will trigger the ACPI GPE
associated with the device to generate an ACPI wake-up event that we
can set up a handler for, provided that everything is configured
correctly.

Unfortunately, the subset of PCI devices that have GPEs associated
with them is quite limited and the other devices have to rely on
the GPEs associated with their upstream bridges and, possibly, the
root bridge to generate ACPI wake-up events in response to PMEs from
them.  Moreover, ACPI-based PCI hotplug also uses ACPI notify
handlers that in general may conflict with the PM notify handlers,
unless this issue is specifically taken care of.

Add ACPI platform support for PCI PME wake-up:
o Add a framework making is possible to use ACPI system notify
  handlers for both PM and hotplug at the same time and to take the
  wake-up GPE sharing into account.
o Add new PCI platform callback ->run_wake() to struct
  pci_platform_pm_ops allowing us to enable/disable the platform to
  generate wake-up events for given device.  Implemet this callback
  for the ACPI platform.
o Define ACPI wake-up handlers for PCI devices and PCI buses and make
  the PCI-ACPI binding code register wake-up notifiers for devices
  associated with wake-up GPEs.
o Add function pci_dev_run_wake() which can be used by PCI drivers to
  check if given device is capable of generating wake-up events at
  run time.

Developed in cooperation with Matthew Garrett <mjg@redhat.com>.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
 drivers/acpi/internal.h            |    2 
 drivers/acpi/pci_bind.c            |   14 +
 drivers/acpi/pci_root.c            |    8 
 drivers/pci/hotplug/acpiphp_glue.c |   23 --
 drivers/pci/pci-acpi.c             |  341 +++++++++++++++++++++++++++++++++++++
 drivers/pci/pci.c                  |   67 +++++++
 drivers/pci/pci.h                  |    7 
 include/acpi/acpi_bus.h            |    4 
 include/linux/pci-acpi.h           |   10 +
 include/linux/pci.h                |    1 
 10 files changed, 460 insertions(+), 17 deletions(-)

Index: linux-2.6/drivers/pci/pci.h
===================================================================
--- linux-2.6.orig/drivers/pci/pci.h
+++ linux-2.6/drivers/pci/pci.h
@@ -35,6 +35,10 @@ int pci_probe_reset_function(struct pci_
  *
  * @sleep_wake: enables/disables the system wake up capability of given device
  *
+ * @run_wake: enables/disables the platform to generate run-time wake-up events
+ *		for given device (the device's wake-up capability has to be
+ *		enabled by @sleep_wake for this feature to work)
+ *
  * If given platform is generally capable of power managing PCI devices, all of
  * these callbacks are mandatory.
  */
@@ -44,12 +48,15 @@ struct pci_platform_pm_ops {
 	pci_power_t (*choose_state)(struct pci_dev *dev);
 	bool (*can_wakeup)(struct pci_dev *dev);
 	int (*sleep_wake)(struct pci_dev *dev, bool enable);
+	int (*run_wake)(struct pci_dev *dev, bool enable);
 };
 
 extern int pci_set_platform_pm(struct pci_platform_pm_ops *ops);
 extern void pci_update_current_state(struct pci_dev *dev, pci_power_t state);
 extern void pci_disable_enabled_device(struct pci_dev *dev);
 extern bool pci_check_pme_status(struct pci_dev *dev);
+extern int __pci_pme_wakeup(struct pci_dev *dev, void *ign);
+extern void pci_pme_wakeup_bus(struct pci_bus *bus);
 extern void pci_pm_init(struct pci_dev *dev);
 extern void platform_pci_wakeup_init(struct pci_dev *dev);
 extern void pci_allocate_cap_save_buffers(struct pci_dev *dev);
Index: linux-2.6/drivers/pci/pci-acpi.c
===================================================================
--- linux-2.6.orig/drivers/pci/pci-acpi.c
+++ linux-2.6/drivers/pci/pci-acpi.c
@@ -16,8 +16,276 @@
 #include <acpi/acpi_bus.h>
 
 #include <linux/pci-acpi.h>
+#include <linux/pm_runtime.h>
 #include "pci.h"
 
+static DEFINE_MUTEX(pci_acpi_notifier_mtx);
+
+struct pci_acpi_notify_data
+{
+	acpi_notify_handler hp_cb;
+	void *hp_data;
+	struct pci_bus *pci_bus;
+	struct pci_dev *pci_dev;
+};
+
+/**
+ * pci_acpi_event_fn - Universal system notification handler.
+ * @handle: ACPI handle of a device the notification is for.
+ * @event: Type of the signaled event.
+ * @ign: The value of this argument is ignored.
+ *
+ * Use @handle to obtain the address of the ACPI device object the event is
+ * signaled for.  If this is a wake-up event, execute the appropriate PME
+ * handler for the bus or device represented by it (or both, if @dev is a
+ * bridge).  If this is not a wake-up event, execute the hotplug notify handler
+ * for @handle.
+ */
+static void pci_acpi_event_fn(acpi_handle handle, u32 event, void *ign)
+{
+	struct acpi_device *dev;
+	struct pci_acpi_notify_data *nd;
+
+	if (ACPI_FAILURE(acpi_bus_get_device(handle, &dev))) {
+		pr_warning("ACPI handle has no context in %s!\n", __func__);
+		return;
+	}
+
+	mutex_lock(&pci_acpi_notifier_mtx);
+
+	nd = dev->bus_data;
+	if (!nd)
+		goto out;
+
+	if (event == ACPI_NOTIFY_DEVICE_WAKE) {
+		if (nd->pci_bus) {
+			pci_pme_wakeup_bus(nd->pci_bus);
+		}
+		if (nd->pci_dev) {
+			pci_check_pme_status(nd->pci_dev);
+			pm_request_resume(&nd->pci_dev->dev);
+		}
+	} else if (nd->hp_cb) {
+		nd->hp_cb(handle, event, nd->hp_data);
+	}
+
+ out:
+	mutex_unlock(&pci_acpi_notifier_mtx);
+}
+
+/**
+ * add_notify_data - Create a new notify data object for given ACPI device.
+ * @dev: Device to create the notify data object for.
+ */
+static struct pci_acpi_notify_data *add_notify_data(struct acpi_device *dev)
+{
+	struct pci_acpi_notify_data *nd;
+
+	nd = kzalloc(sizeof(*nd), GFP_KERNEL);
+	if (!nd)
+		return NULL;
+
+	dev->bus_data = nd;
+	return nd;
+}
+
+/**
+ * remove_notify_data - Remove the notify data object from given ACPI device.
+ * @dev: Device to remove the notify data object from.
+ */
+static void remove_notify_data(struct acpi_device *dev)
+{
+	kfree(dev->bus_data);
+	dev->bus_data = NULL;
+}
+
+/**
+ * pci_acpi_add_hp_notifier - Register a hotplug notifier for given device.
+ * @handle: ACPI handle of the device to register the notifier for.
+ * @handler: Callback to execute for hotplug events related to @handle.
+ * @context: Pointer to the context data to pass to @handler.
+ *
+ * Use @handle to get an ACPI device object and check if there is a notify data
+ * object for it.  If this is the case, add @handler and @context to the
+ * existing notify data object, unless there already is a hotplug handler in
+ * there.  Otherwise, create a new notify data object for the ACPI device
+ * associated with @handle and add @handler and @context to it.
+ */
+acpi_status pci_acpi_add_hp_notifier(acpi_handle handle,
+				     acpi_notify_handler handler, void *context)
+{
+	struct pci_acpi_notify_data *nd;
+	struct acpi_device *dev;
+	acpi_status status = AE_OK;
+
+	if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &dev)))
+		return AE_NOT_FOUND;
+
+	mutex_lock(&pci_acpi_notifier_mtx);
+
+	nd = dev->bus_data;
+	if (nd) {
+		if (!nd->hp_cb)
+			goto add;
+
+		status = AE_ALREADY_EXISTS;
+		goto out;
+	}
+
+	nd = add_notify_data(dev);
+	if (!nd) {
+		status = AE_NO_MEMORY;
+		goto out;
+	}
+
+	status = acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
+						pci_acpi_event_fn, NULL);
+	if (ACPI_FAILURE(status)) {
+		remove_notify_data(dev);
+		goto out;
+	}
+
+ add:
+	nd->hp_cb = handler;
+	nd->hp_data = context;
+
+ out:
+	mutex_unlock(&pci_acpi_notifier_mtx);
+
+	return status;
+}
+EXPORT_SYMBOL_GPL(pci_acpi_add_hp_notifier);
+
+/**
+ * pci_acpi_remove_hp_notifier - Unregister a hotplug notifier for given device.
+ * @handle: ACPI handle of the device to unregister the notifier for.
+ * @handler: Callback executed for hotplug events related to @handle.
+ *
+ * Remove the hotplug callback and the pointer to the hotplug context data from
+ * the notify data object belonging to the device represented by @handle.  If
+ * the notify data object is not necessary any more, remove it altogether.
+ */
+acpi_status pci_acpi_remove_hp_notifier(acpi_handle handle,
+					acpi_notify_handler handler)
+{
+	struct pci_acpi_notify_data *nd;
+	struct acpi_device *dev;
+	acpi_status status = AE_NOT_FOUND;
+
+	if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &dev)))
+		return AE_NOT_FOUND;
+
+	mutex_lock(&pci_acpi_notifier_mtx);
+
+	nd = dev->bus_data;
+	if (!nd)
+		goto out;
+
+	nd->hp_data = NULL;
+	nd->hp_cb = NULL;
+
+	if (nd->pci_bus || nd->pci_dev)
+		goto out;
+
+	status = acpi_remove_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
+						pci_acpi_event_fn);
+	remove_notify_data(dev);
+
+ out:
+	mutex_unlock(&pci_acpi_notifier_mtx);
+	return status;
+}
+EXPORT_SYMBOL_GPL(pci_acpi_remove_hp_notifier);
+
+/**
+ * pci_acpi_add_pm_notifier - Register PM notifier for given device.
+ * @dev: ACPI device to add the notifier for.
+ * @pci_dev: PCI device to check for the PME status if an event is signaled.
+ * @pci_bus: PCI bus to walk (checking PME status) if an event is signaled.
+ *
+ * Check if there is a notify data object for @dev and if that is the case, add
+ * @pci_dev to it as the device whose PME status should be checked whenever a PM
+ * event is signaled for @dev.  Also, add @pci_bus to it as the bus to walk
+ * checking the PME status of all devices on it whenever a PM event is signaled
+ * for @dev.
+ *
+ * Otherwise, create a new notify data object for @dev and add both @pci_dev and
+ * @pci_bus to it.
+ *
+ * NOTE: @dev need not be a run-wake or wake-up device to be a valid source of
+ * PM wake-up events.  For example, wake-up events may be generated for bridges
+ * if one of the devices below the bridge is signaling PME, even if the bridge
+ * itself doesn't have a wake-up GPE associated with it.
+ */
+acpi_status pci_acpi_add_pm_notifier(struct acpi_device *dev,
+				     struct pci_dev *pci_dev,
+				     struct pci_bus *pci_bus)
+{
+	struct pci_acpi_notify_data *nd;
+	acpi_status status = AE_OK;
+
+	mutex_lock(&pci_acpi_notifier_mtx);
+
+	nd = dev->bus_data;
+	if (nd)
+		goto add;
+
+	nd = add_notify_data(dev);
+	if (!nd) {
+		status = AE_NO_MEMORY;
+		goto out;
+	}
+
+	status = acpi_install_notify_handler(dev->handle, ACPI_SYSTEM_NOTIFY,
+						pci_acpi_event_fn, NULL);
+	if (ACPI_FAILURE(status)) {
+		remove_notify_data(dev);
+		goto out;
+	}
+
+ add:
+	nd->pci_dev = pci_dev;
+	nd->pci_bus = pci_bus;
+
+ out:
+	mutex_unlock(&pci_acpi_notifier_mtx);
+	return status;
+}
+
+/**
+ * pci_acpi_remove_pm_notifier - Unregister PM notifier for given device.
+ * @dev: ACPI device to remove the notifier from.
+ *
+ * Find the notify data object for @dev and clear its @pci_dev and @pci_bus
+ * fields.  If the notify data object is not necessary any more after that,
+ * remove it too.
+ */
+acpi_status pci_acpi_remove_pm_notifier(struct acpi_device *dev)
+{
+	struct pci_acpi_notify_data *nd;
+	acpi_status status = AE_NOT_FOUND;
+
+	mutex_lock(&pci_acpi_notifier_mtx);
+
+	nd = dev->bus_data;
+	if (!nd)
+		goto out;
+
+	nd->pci_dev = NULL;
+	nd->pci_bus = NULL;
+
+	if (nd->hp_cb)
+		goto out;
+
+	status = acpi_remove_notify_handler(dev->handle, ACPI_SYSTEM_NOTIFY,
+						pci_acpi_event_fn);
+	remove_notify_data(dev);
+
+ out:
+	mutex_unlock(&pci_acpi_notifier_mtx);
+	return status;
+}
+
 /*
  * _SxD returns the D-state with the highest power
  * (lowest D-state number) supported in the S-state "x".
@@ -131,12 +399,85 @@ static int acpi_pci_sleep_wake(struct pc
 	return 0;
 }
 
+/**
+ * acpi_dev_run_wake - Enable/disable wake-up for given device.
+ * @phys_dev: Device to enable/disable the platform to wake-up the system for.
+ * @enable: Whether enable or disable the wake-up functionality.
+ *
+ * Find the ACPI device object corresponding to @pci_dev and try to
+ * enable/disable the GPE associated with it.
+ */
+static int acpi_dev_run_wake(struct device *phys_dev, bool enable)
+{
+	struct acpi_device *dev;
+	acpi_handle handle;
+	int error = -ENODEV;
+
+	if (!device_run_wake(phys_dev))
+		return -EINVAL;
+
+	handle = DEVICE_ACPI_HANDLE(phys_dev);
+	if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &dev))) {
+		dev_dbg(phys_dev, "ACPI handle has no context in %s!\n",
+			__func__);
+		return -ENODEV;
+	}
+
+	if (enable) {
+		if (!dev->wakeup.run_wake_count++) {
+			acpi_enable_wakeup_device_power(dev, ACPI_STATE_S0);
+			acpi_ref_runtime_gpe(dev->wakeup.gpe_device,
+						dev->wakeup.gpe_number);
+		}
+	} else if (dev->wakeup.run_wake_count > 0) {
+		if (!--dev->wakeup.run_wake_count) {
+			acpi_unref_runtime_gpe(dev->wakeup.gpe_device,
+						dev->wakeup.gpe_number);
+			acpi_disable_wakeup_device_power(dev);
+		}
+	} else {
+		error = -EALREADY;
+	}
+
+	return error;
+}
+
+static void acpi_pci_propagate_run_wake(struct pci_bus *bus, bool enable)
+{
+	while (bus->parent) {
+		struct pci_dev *bridge = bus->self;
+
+		if (bridge->pme_interrupt)
+			return;
+		if (!acpi_dev_run_wake(&bridge->dev, enable))
+			return;
+		bus = bus->parent;
+	}
+
+	/* We have reached the root bus. */
+	if (bus->bridge)
+		acpi_dev_run_wake(bus->bridge, enable);
+}
+
+static int acpi_pci_run_wake(struct pci_dev *dev, bool enable)
+{
+	if (dev->pme_interrupt)
+		return 0;
+
+	if (!acpi_dev_run_wake(&dev->dev, enable))
+		return 0;
+
+	acpi_pci_propagate_run_wake(dev->bus, enable);
+	return 0;
+}
+
 static struct pci_platform_pm_ops acpi_pci_platform_pm = {
 	.is_manageable = acpi_pci_power_manageable,
 	.set_state = acpi_pci_set_power_state,
 	.choose_state = acpi_pci_choose_state,
 	.can_wakeup = acpi_pci_can_wakeup,
 	.sleep_wake = acpi_pci_sleep_wake,
+	.run_wake = acpi_pci_run_wake,
 };
 
 /* ACPI bus type */
Index: linux-2.6/drivers/pci/pci.c
===================================================================
--- linux-2.6.orig/drivers/pci/pci.c
+++ linux-2.6/drivers/pci/pci.c
@@ -20,6 +20,7 @@
 #include <linux/pm_wakeup.h>
 #include <linux/interrupt.h>
 #include <linux/device.h>
+#include <linux/pm_runtime.h>
 #include <asm/setup.h>
 #include "pci.h"
 
@@ -462,6 +463,12 @@ static inline int platform_pci_sleep_wak
 			pci_platform_pm->sleep_wake(dev, enable) : -ENODEV;
 }
 
+static inline int platform_pci_run_wake(struct pci_dev *dev, bool enable)
+{
+	return pci_platform_pm ?
+			pci_platform_pm->run_wake(dev, enable) : -ENODEV;
+}
+
 /**
  * pci_raw_set_power_state - Use PCI PM registers to set the power state of
  *                           given PCI device
@@ -1230,6 +1237,31 @@ bool pci_check_pme_status(struct pci_dev
 }
 
 /**
+ * pci_pme_wakeup - Wake up a PCI device if its PME Status bit is set.
+ * @dev: Device to handle.
+ * @ign: Ignored.
+ *
+ * Check if @dev has generated PME and queue a resume request for it in that
+ * case.
+ */
+static int pci_pme_wakeup(struct pci_dev *dev, void *ign)
+{
+	if (pci_check_pme_status(dev))
+		pm_request_resume(&dev->dev);
+	return 0;
+}
+
+/**
+ * pci_pme_wakeup_bus - Walk given bus and wake up devices on it, if necessary.
+ * @bus: Top bus of the subtree to walk.
+ */
+void pci_pme_wakeup_bus(struct pci_bus *bus)
+{
+	if (bus)
+		pci_walk_bus(bus, pci_pme_wakeup, NULL);
+}
+
+/**
  * pci_pme_capable - check the capability of PCI device to generate PME#
  * @dev: PCI device to handle.
  * @state: PCI state from which device will issue PME#.
@@ -1434,6 +1466,41 @@ int pci_back_from_sleep(struct pci_dev *
 }
 
 /**
+ * pci_dev_run_wake - Check if device can generate run-time wake-up events.
+ * @dev: Device to check.
+ *
+ * Return true if the device itself is cabable of generating wake-up events
+ * (through the platform or using the native PCIe PME) or if the device supports
+ * PME and one of its upstream bridges can generate wake-up events.
+ */
+bool pci_dev_run_wake(struct pci_dev *dev)
+{
+	struct pci_bus *bus = dev->bus;
+
+	if (device_run_wake(&dev->dev))
+		return true;
+
+	if (!dev->pme_support)
+		return false;
+
+	while (bus->parent) {
+		struct pci_dev *bridge = bus->self;
+
+		if (device_run_wake(&bridge->dev))
+			return true;
+
+		bus = bus->parent;
+	}
+
+	/* We have reached the root bus. */
+	if (bus->bridge)
+		return device_run_wake(bus->bridge);
+
+	return false;
+}
+EXPORT_SYMBOL_GPL(pci_dev_run_wake);
+
+/**
  * pci_pm_init - Initialize PM functions of given PCI device
  * @dev: PCI device to handle.
  */
Index: linux-2.6/include/linux/pci-acpi.h
===================================================================
--- linux-2.6.orig/include/linux/pci-acpi.h
+++ linux-2.6/include/linux/pci-acpi.h
@@ -11,6 +11,16 @@
 #include <linux/acpi.h>
 
 #ifdef CONFIG_ACPI
+extern acpi_status pci_acpi_add_hp_notifier(acpi_handle handle,
+					     acpi_notify_handler handler,
+					     void *context);
+extern acpi_status pci_acpi_remove_hp_notifier(acpi_handle handle,
+					       acpi_notify_handler handler);
+extern acpi_status pci_acpi_add_pm_notifier(struct acpi_device *dev,
+					     struct pci_dev *pci_dev,
+					     struct pci_bus *pci_bus);
+extern acpi_status pci_acpi_remove_pm_notifier(struct acpi_device *dev);
+
 static inline acpi_handle acpi_find_root_bridge_handle(struct pci_dev *pdev)
 {
 	struct pci_bus *pbus = pdev->bus;
Index: linux-2.6/drivers/acpi/pci_bind.c
===================================================================
--- linux-2.6.orig/drivers/acpi/pci_bind.c
+++ linux-2.6/drivers/acpi/pci_bind.c
@@ -26,7 +26,9 @@
 #include <linux/kernel.h>
 #include <linux/types.h>
 #include <linux/pci.h>
+#include <linux/pci-acpi.h>
 #include <linux/acpi.h>
+#include <linux/pm_runtime.h>
 #include <acpi/acpi_bus.h>
 #include <acpi/acpi_drivers.h>
 
@@ -38,7 +40,13 @@ static int acpi_pci_unbind(struct acpi_d
 	struct pci_dev *dev;
 
 	dev = acpi_get_pci_dev(device->handle);
-	if (!dev || !dev->subordinate)
+	if (!dev)
+		goto out;
+
+	device_set_run_wake(&dev->dev, false);
+	pci_acpi_remove_pm_notifier(device);
+
+	if (!dev->subordinate)
 		goto out;
 
 	acpi_pci_irq_del_prt(dev->subordinate);
@@ -62,6 +70,10 @@ static int acpi_pci_bind(struct acpi_dev
 	if (!dev)
 		return 0;
 
+	pci_acpi_add_pm_notifier(device, dev, dev->subordinate);
+	if (device->wakeup.flags.run_wake)
+		device_set_run_wake(&dev->dev, true);
+
 	/*
 	 * Install the 'bind' function to facilitate callbacks for
 	 * children of the P2P bridge.
Index: linux-2.6/drivers/pci/hotplug/acpiphp_glue.c
===================================================================
--- linux-2.6.orig/drivers/pci/hotplug/acpiphp_glue.c
+++ linux-2.6/drivers/pci/hotplug/acpiphp_glue.c
@@ -236,8 +236,7 @@ register_slot(acpi_handle handle, u32 lv
 
 	/* install notify handler */
 	if (!(newfunc->flags & FUNC_HAS_DCK)) {
-		status = acpi_install_notify_handler(handle,
-					     ACPI_SYSTEM_NOTIFY,
+		status = pci_acpi_add_hp_notifier(handle,
 					     handle_hotplug_event_func,
 					     newfunc);
 
@@ -288,14 +287,12 @@ static void init_bridge_misc(struct acpi
 	/* install notify handler */
 	if (bridge->type != BRIDGE_TYPE_HOST) {
 		if ((bridge->flags & BRIDGE_HAS_EJ0) && bridge->func) {
-			status = acpi_remove_notify_handler(bridge->func->handle,
-						ACPI_SYSTEM_NOTIFY,
+			status = pci_acpi_remove_hp_notifier(bridge->func->handle,
 						handle_hotplug_event_func);
 			if (ACPI_FAILURE(status))
 				err("failed to remove notify handler\n");
 		}
-		status = acpi_install_notify_handler(bridge->handle,
-					     ACPI_SYSTEM_NOTIFY,
+		status = pci_acpi_add_hp_notifier(bridge->handle,
 					     handle_hotplug_event_bridge,
 					     bridge);
 
@@ -505,15 +502,14 @@ static void cleanup_bridge(struct acpiph
 	acpi_status status;
 	acpi_handle handle = bridge->handle;
 
-	status = acpi_remove_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
+	status = pci_acpi_remove_hp_notifier(handle,
 					    handle_hotplug_event_bridge);
 	if (ACPI_FAILURE(status))
 		err("failed to remove notify handler\n");
 
 	if ((bridge->type != BRIDGE_TYPE_HOST) &&
 	    ((bridge->flags & BRIDGE_HAS_EJ0) && bridge->func)) {
-		status = acpi_install_notify_handler(bridge->func->handle,
-						ACPI_SYSTEM_NOTIFY,
+		status = pci_acpi_add_hp_notifier(bridge->func->handle,
 						handle_hotplug_event_func,
 						bridge->func);
 		if (ACPI_FAILURE(status))
@@ -529,8 +525,7 @@ static void cleanup_bridge(struct acpiph
 				unregister_dock_notifier(&func->nb);
 			}
 			if (!(func->flags & FUNC_HAS_DCK)) {
-				status = acpi_remove_notify_handler(func->handle,
-						ACPI_SYSTEM_NOTIFY,
+				status = pci_acpi_remove_hp_notifier(func->handle,
 						handle_hotplug_event_func);
 				if (ACPI_FAILURE(status))
 					err("failed to remove notify handler\n");
@@ -592,7 +587,7 @@ static void remove_bridge(acpi_handle ha
 	if (bridge)
 		cleanup_bridge(bridge);
 	else
-		acpi_remove_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
+		pci_acpi_remove_hp_notifier(handle,
 					   handle_hotplug_event_bridge);
 }
 
@@ -1278,8 +1273,8 @@ find_root_bridges(acpi_handle handle, u3
 	int *count = (int *)context;
 
 	if (acpi_is_root_bridge(handle)) {
-		acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
-				handle_hotplug_event_bridge, NULL);
+		pci_acpi_add_hp_notifier(handle,
+					handle_hotplug_event_bridge, NULL);
 			(*count)++;
 	}
 	return AE_OK ;
Index: linux-2.6/include/linux/pci.h
===================================================================
--- linux-2.6.orig/include/linux/pci.h
+++ linux-2.6/include/linux/pci.h
@@ -756,6 +756,7 @@ int pci_wake_from_d3(struct pci_dev *dev
 pci_power_t pci_target_state(struct pci_dev *dev);
 int pci_prepare_to_sleep(struct pci_dev *dev);
 int pci_back_from_sleep(struct pci_dev *dev);
+bool pci_dev_run_wake(struct pci_dev *dev);
 
 /* Functions for PCI Hotplug drivers to use */
 int pci_bus_find_capability(struct pci_bus *bus, unsigned int devfn, int cap);
Index: linux-2.6/include/acpi/acpi_bus.h
===================================================================
--- linux-2.6.orig/include/acpi/acpi_bus.h
+++ linux-2.6/include/acpi/acpi_bus.h
@@ -282,6 +282,7 @@ struct acpi_device {
 	struct device dev;
 	struct acpi_bus_ops bus_ops;	/* workaround for different code path for hotplug */
 	enum acpi_bus_removal_type removal_type;	/* indicate for different removal type */
+	void *bus_data;
 };
 
 static inline void *acpi_driver_data(struct acpi_device *d)
@@ -388,6 +389,9 @@ acpi_handle acpi_get_pci_rootbridge_hand
 struct acpi_pci_root *acpi_pci_find_root(acpi_handle handle);
 #define DEVICE_ACPI_HANDLE(dev) ((acpi_handle)((dev)->archdata.acpi_handle))
 
+int acpi_enable_wakeup_device_power(struct acpi_device *dev, int state);
+int acpi_disable_wakeup_device_power(struct acpi_device *dev);
+
 #ifdef CONFIG_PM_SLEEP
 int acpi_pm_device_sleep_state(struct device *, int *);
 int acpi_pm_device_sleep_wake(struct device *, bool);
Index: linux-2.6/drivers/acpi/pci_root.c
===================================================================
--- linux-2.6.orig/drivers/acpi/pci_root.c
+++ linux-2.6/drivers/acpi/pci_root.c
@@ -30,6 +30,7 @@
 #include <linux/proc_fs.h>
 #include <linux/spinlock.h>
 #include <linux/pm.h>
+#include <linux/pm_runtime.h>
 #include <linux/pci.h>
 #include <linux/pci-acpi.h>
 #include <linux/acpi.h>
@@ -528,6 +529,10 @@ static int __devinit acpi_pci_root_add(s
 	if (flags != base_flags)
 		acpi_pci_osc_support(root, flags);
 
+	pci_acpi_add_pm_notifier(device, NULL, root->bus);
+	if (device->wakeup.flags.run_wake)
+		device_set_run_wake(root->bus->bridge, true);
+
 	return 0;
 
 end:
@@ -549,6 +554,9 @@ static int acpi_pci_root_remove(struct a
 {
 	struct acpi_pci_root *root = acpi_driver_data(device);
 
+	device_set_run_wake(root->bus->bridge, false);
+	pci_acpi_remove_pm_notifier(device);
+
 	kfree(root);
 	return 0;
 }
Index: linux-2.6/drivers/acpi/internal.h
===================================================================
--- linux-2.6.orig/drivers/acpi/internal.h
+++ linux-2.6/drivers/acpi/internal.h
@@ -36,8 +36,6 @@ static inline int acpi_debug_init(void) 
 int acpi_power_init(void);
 int acpi_device_sleep_wake(struct acpi_device *dev,
                            int enable, int sleep_state, int dev_state);
-int acpi_enable_wakeup_device_power(struct acpi_device *dev, int sleep_state);
-int acpi_disable_wakeup_device_power(struct acpi_device *dev);
 int acpi_power_get_inferred_state(struct acpi_device *device);
 int acpi_power_transition(struct acpi_device *device, int state);
 extern int acpi_power_nocheck;

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 4/12] PCI PM: Make it possible to force using INTx for PCIe PME signaling
  2010-01-06 23:00       ` Rafael J. Wysocki
@ 2010-01-08 20:08         ` Len Brown
  2010-01-08 20:25           ` Greg KH
  2010-01-08 20:25           ` Greg KH
  2010-01-08 20:08         ` Len Brown
  1 sibling, 2 replies; 68+ messages in thread
From: Len Brown @ 2010-01-08 20:08 UTC (permalink / raw)
  To: Rafael J. Wysocki
  Cc: Matthew Garrett, Jesse Barnes, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu

> > > This scares me a little... seems like we're not restoring some state on
> > > resume that's needed for MSIs to work?  Looks fine though.
> > 
> > Yeah, that was my concern. We debugged this a little on IRC and didn't 
> > get anywhere, though, so probably need to find someone who knows more 
> > about MSIs...
> 
> I'd rather say someone who knows more about ICH7. :-)
> 
> MSIs work just fine after resume for the other devices in the same box,
> so I'd say it's a chipset issue.

Hmmm, do all the ICH7 boxes behave the same way?

Yesterday MSI announced a netbook running Suse/Moblin,
so there is hope for more technical work w/ MSI to support Linux...

It is a bit worrysome to go out of the gate to need a DMI blacklist
for relatively new hardware.

cheers,
Len Brown, Intel Open Source Technology Center

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 4/12] PCI PM: Make it possible to force using INTx for PCIe PME signaling
  2010-01-06 23:00       ` Rafael J. Wysocki
  2010-01-08 20:08         ` Len Brown
@ 2010-01-08 20:08         ` Len Brown
  1 sibling, 0 replies; 68+ messages in thread
From: Len Brown @ 2010-01-08 20:08 UTC (permalink / raw)
  To: Rafael J. Wysocki
  Cc: Linux PCI, LKML, Jesse Barnes, ACPI Devel Maling List,
	Francois Romieu, pm list

> > > This scares me a little... seems like we're not restoring some state on
> > > resume that's needed for MSIs to work?  Looks fine though.
> > 
> > Yeah, that was my concern. We debugged this a little on IRC and didn't 
> > get anywhere, though, so probably need to find someone who knows more 
> > about MSIs...
> 
> I'd rather say someone who knows more about ICH7. :-)
> 
> MSIs work just fine after resume for the other devices in the same box,
> so I'd say it's a chipset issue.

Hmmm, do all the ICH7 boxes behave the same way?

Yesterday MSI announced a netbook running Suse/Moblin,
so there is hope for more technical work w/ MSI to support Linux...

It is a bit worrysome to go out of the gate to need a DMI blacklist
for relatively new hardware.

cheers,
Len Brown, Intel Open Source Technology Center

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 4/12] PCI PM: Make it possible to force using INTx for PCIe PME signaling
  2010-01-08 20:08         ` Len Brown
  2010-01-08 20:25           ` Greg KH
@ 2010-01-08 20:25           ` Greg KH
  1 sibling, 0 replies; 68+ messages in thread
From: Greg KH @ 2010-01-08 20:25 UTC (permalink / raw)
  To: Len Brown
  Cc: Rafael J. Wysocki, Matthew Garrett, Jesse Barnes, LKML, pm list,
	Alan Stern, ACPI Devel Maling List, Linux PCI, Oliver Neukum,
	Bjorn Helgaas, Shaohua Li, Francois Romieu

On Fri, Jan 08, 2010 at 03:08:03PM -0500, Len Brown wrote:
> Yesterday MSI announced a netbook running Suse/Moblin,
> so there is hope for more technical work w/ MSI to support Linux...

Yes, we are currently working on a patch for the existing msi driver to
get some of the keys to work a bit better.  It's still going through
review back-and-forth between me and the developer, and will be posted
soon.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 4/12] PCI PM: Make it possible to force using INTx for PCIe PME signaling
  2010-01-08 20:08         ` Len Brown
@ 2010-01-08 20:25           ` Greg KH
  2010-01-08 20:25           ` Greg KH
  1 sibling, 0 replies; 68+ messages in thread
From: Greg KH @ 2010-01-08 20:25 UTC (permalink / raw)
  To: Len Brown
  Cc: Linux PCI, LKML, Jesse Barnes, ACPI Devel Maling List,
	Francois Romieu, pm list

On Fri, Jan 08, 2010 at 03:08:03PM -0500, Len Brown wrote:
> Yesterday MSI announced a netbook running Suse/Moblin,
> so there is hope for more technical work w/ MSI to support Linux...

Yes, we are currently working on a patch for the existing msi driver to
get some of the keys to work a bit better.  It's still going through
review back-and-forth between me and the developer, and will be posted
soon.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 8/12] ACPI / PM: Add more run-time wake-up fields
  2009-12-27 20:05 ` [PATCH 8/12] ACPI / PM: Add more run-time wake-up fields Rafael J. Wysocki
@ 2010-01-08 20:39   ` Len Brown
  2010-01-08 23:27     ` Rafael J. Wysocki
  2010-01-08 23:27     ` Rafael J. Wysocki
  2010-01-08 20:39   ` Len Brown
  1 sibling, 2 replies; 68+ messages in thread
From: Len Brown @ 2010-01-08 20:39 UTC (permalink / raw)
  To: Rafael J. Wysocki
  Cc: Jesse Barnes, Matthew Garrett, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu

> Use the run_wake flag to mark all devices for which run-time wake-up
> events may be generated by the platform.  Introduce a new wake-up
> flag, special, for marking devices that should be permanently enabled
> to generate run-time events.

how about calling the flag "always_enabled" rather than "special"?

otherwise,
Acked-by: Len Brown <len.brown@intel.com>


^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 8/12] ACPI / PM: Add more run-time wake-up fields
  2009-12-27 20:05 ` [PATCH 8/12] ACPI / PM: Add more run-time wake-up fields Rafael J. Wysocki
  2010-01-08 20:39   ` Len Brown
@ 2010-01-08 20:39   ` Len Brown
  1 sibling, 0 replies; 68+ messages in thread
From: Len Brown @ 2010-01-08 20:39 UTC (permalink / raw)
  To: Rafael J. Wysocki
  Cc: Linux PCI, LKML, Jesse Barnes, ACPI Devel Maling List,
	Francois Romieu, pm list, Matthew Garrett

> Use the run_wake flag to mark all devices for which run-time wake-up
> events may be generated by the platform.  Introduce a new wake-up
> flag, special, for marking devices that should be permanently enabled
> to generate run-time events.

how about calling the flag "always_enabled" rather than "special"?

otherwise,
Acked-by: Len Brown <len.brown@intel.com>

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 8/12] ACPI / PM: Add more run-time wake-up fields
  2010-01-08 20:39   ` Len Brown
  2010-01-08 23:27     ` Rafael J. Wysocki
@ 2010-01-08 23:27     ` Rafael J. Wysocki
  1 sibling, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2010-01-08 23:27 UTC (permalink / raw)
  To: Len Brown
  Cc: Jesse Barnes, Matthew Garrett, LKML, pm list, Alan Stern,
	ACPI Devel Maling List, Linux PCI, Oliver Neukum, Bjorn Helgaas,
	Shaohua Li, Francois Romieu

On Friday 08 January 2010, Len Brown wrote:
> > Use the run_wake flag to mark all devices for which run-time wake-up
> > events may be generated by the platform.  Introduce a new wake-up
> > flag, special, for marking devices that should be permanently enabled
> > to generate run-time events.
> 
> how about calling the flag "always_enabled" rather than "special"?

OK, I'll do that.
 
> otherwise,
> Acked-by: Len Brown <len.brown@intel.com>

Thanks!

^ permalink raw reply	[flat|nested] 68+ messages in thread

* Re: [PATCH 8/12] ACPI / PM: Add more run-time wake-up fields
  2010-01-08 20:39   ` Len Brown
@ 2010-01-08 23:27     ` Rafael J. Wysocki
  2010-01-08 23:27     ` Rafael J. Wysocki
  1 sibling, 0 replies; 68+ messages in thread
From: Rafael J. Wysocki @ 2010-01-08 23:27 UTC (permalink / raw)
  To: Len Brown
  Cc: Linux PCI, LKML, Jesse Barnes, ACPI Devel Maling List,
	Francois Romieu, pm list, Matthew Garrett

On Friday 08 January 2010, Len Brown wrote:
> > Use the run_wake flag to mark all devices for which run-time wake-up
> > events may be generated by the platform.  Introduce a new wake-up
> > flag, special, for marking devices that should be permanently enabled
> > to generate run-time events.
> 
> how about calling the flag "always_enabled" rather than "special"?

OK, I'll do that.
 
> otherwise,
> Acked-by: Len Brown <len.brown@intel.com>

Thanks!

^ permalink raw reply	[flat|nested] 68+ messages in thread

end of thread, other threads:[~2010-01-08 23:27 UTC | newest]

Thread overview: 68+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2009-12-27 19:57 [PATCH 0/12] PCI run-time PM support (rev. 2) Rafael J. Wysocki
2009-12-27 19:59 ` [PATCH 1/12] PCI PM: Add function for checking PME status of devices Rafael J. Wysocki
2009-12-27 19:59   ` Rafael J. Wysocki
2010-01-06 21:46   ` Jesse Barnes
2010-01-06 21:46   ` Jesse Barnes
2009-12-27 20:00 ` [PATCH 2/12] PCI / PM: Propagate wake-up enable for PCIe devices too Rafael J. Wysocki
2010-01-04 23:40   ` Jesse Barnes
2010-01-05 21:27     ` Rafael J. Wysocki
2010-01-05 21:27     ` Rafael J. Wysocki
2010-01-04 23:40   ` Jesse Barnes
2009-12-27 20:00 ` Rafael J. Wysocki
2009-12-27 20:01 ` [PATCH 3/12] PCI PM: PCIe PME root port service driver (rev. 5) Rafael J. Wysocki
2009-12-27 20:01 ` Rafael J. Wysocki
2010-01-06 21:53   ` Jesse Barnes
2010-01-06 21:53   ` Jesse Barnes
2009-12-27 20:02 ` [PATCH 4/12] PCI PM: Make it possible to force using INTx for PCIe PME signaling Rafael J. Wysocki
2009-12-27 20:02   ` Rafael J. Wysocki
2010-01-06 21:56   ` Jesse Barnes
2010-01-06 21:56   ` Jesse Barnes
2010-01-06 22:02     ` Matthew Garrett
2010-01-06 23:00       ` Rafael J. Wysocki
2010-01-06 23:00       ` Rafael J. Wysocki
2010-01-08 20:08         ` Len Brown
2010-01-08 20:25           ` Greg KH
2010-01-08 20:25           ` Greg KH
2010-01-08 20:08         ` Len Brown
2010-01-06 22:02     ` Matthew Garrett
2009-12-27 20:03 ` [PATCH 5/12] ACPI: Add infrastructure for refcounting GPE consumers Rafael J. Wysocki
2009-12-27 20:03 ` Rafael J. Wysocki
2009-12-27 20:03 ` [PATCH 6/12] ACPI: Add support for new refcounted GPE API to drivers Rafael J. Wysocki
2009-12-27 20:03   ` Rafael J. Wysocki
2009-12-27 20:04 ` [PATCH 7/12] ACPI: Remove old GPE API and transition code entirely to new one Rafael J. Wysocki
2009-12-27 20:04 ` Rafael J. Wysocki
2009-12-27 20:05 ` [PATCH 8/12] ACPI / PM: Add more run-time wake-up fields Rafael J. Wysocki
2010-01-08 20:39   ` Len Brown
2010-01-08 23:27     ` Rafael J. Wysocki
2010-01-08 23:27     ` Rafael J. Wysocki
2010-01-08 20:39   ` Len Brown
2009-12-27 20:05 ` Rafael J. Wysocki
2009-12-27 20:06 ` [PATCH 9/12] ACPI / PM: Introduce acpi_pm_wakeup_power() Rafael J. Wysocki
2010-01-06 22:00   ` Jesse Barnes
2010-01-06 23:11     ` Rafael J. Wysocki
2010-01-06 23:11     ` Rafael J. Wysocki
2010-01-07 21:11       ` Rafael J. Wysocki
2010-01-07 21:11       ` Rafael J. Wysocki
2010-01-06 22:00   ` Jesse Barnes
2009-12-27 20:06 ` Rafael J. Wysocki
2009-12-27 20:07 ` [PATCH 10/12] PCI / ACPI / PM: Platform support for PCI PME wake-up (rev. 6) Rafael J. Wysocki
2010-01-06 22:04   ` Jesse Barnes
2010-01-07 21:18     ` Rafael J. Wysocki
2010-01-07 21:18     ` Rafael J. Wysocki
2010-01-06 22:04   ` Jesse Barnes
2009-12-27 20:07 ` Rafael J. Wysocki
2009-12-27 20:08 ` [PATCH 11/12] PCI PM: Run-time callbacks for PCI bus type (rev. 2) Rafael J. Wysocki
2009-12-27 20:08 ` Rafael J. Wysocki
2010-01-06 22:06   ` Jesse Barnes
2010-01-06 22:06   ` Jesse Barnes
2009-12-27 20:11 ` [PATCH 12/12] PM / r8169: Add simplified run-time PM support Rafael J. Wysocki
2009-12-27 20:11 ` Rafael J. Wysocki
2010-01-01 19:06   ` [PATCH 12/12] PM / r8169: Add simplified run-time PM support (rev. 2) Rafael J. Wysocki
2010-01-01 19:06   ` Rafael J. Wysocki
2010-01-01  1:29 ` [PATCH][RFC] e1000e: Add basic runtime PM support (was: [PATCH 0/12] PCI run-time PM support (rev. 2)) Rafael J. Wysocki
2010-01-01  1:29 ` Rafael J. Wysocki
2010-01-01  1:29   ` Rafael J. Wysocki
2010-01-01 19:03   ` [PATCH][RFC] e1000e: Add basic runtime PM support (rev. 2) " Rafael J. Wysocki
2010-01-01 21:51     ` [PATCH][RFC] e1000e: Add basic runtime PM support (rev. 3) " Rafael J. Wysocki
2010-01-01 21:51     ` Rafael J. Wysocki
2010-01-01 19:03   ` [PATCH][RFC] e1000e: Add basic runtime PM support (rev. 2) " Rafael J. Wysocki

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.