===================================================================
@@ -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
===================================================================
@@ -11,3 +11,5 @@ obj-$(CONFIG_PCIEPORTBUS) += pcieportdrv
# Build PCI Express AER if needed
obj-$(CONFIG_PCIEAER) += aer/
+
+obj-$(CONFIG_PCIE_PME) += pcie_pme.o
===================================================================
@@ -0,0 +1,427 @@
+/*
+ * PCIe Native PME support
+ *
+ * Copyright (C) 2007 - 2009 Intel Corp
+ * Shaohua Li <shaohua.li@intel.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. 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"
+
+#define PCI_EXP_RTSTA_PME 0x10000 /* PME status */
+#define PCI_EXP_RTSTA_PEND 0x20000 /* PME pending */
+
+static int disabled;
+/*
+ * It appears a lot of BIOS doesn't implement ACPI _OSC correctly, so let's
+ * force to enable PCIe PME by default
+ */
+static int force = 1;
+
+static int __init pcie_pme_setup(char *str)
+{
+ if (!strcmp(str, "off"))
+ disabled = 1;
+ else if (!strcmp(str, "strict"))
+ force = 0;
+ return 1;
+}
+__setup("pcie_pme=", pcie_pme_setup);
+
+struct pcie_pme_service_data {
+ spinlock_t lock;
+ struct pcie_device *dev;
+ struct work_struct work;
+ bool in_exiting; /* driver is exiting, don't reenable interrupt */
+};
+
+/*
+ * pcie_pme_enable_interrupt - enable/disable PCIe PME report interrupt
+ * @pdev: PCI root port
+ * @enable: enable or disable interrupt
+ */
+static inline void pcie_pme_enable_interrupt(struct pci_dev *pdev,
+ bool enable)
+{
+ int pos;
+ u16 rtctl;
+
+ pos = pci_find_capability(pdev, PCI_CAP_ID_EXP);
+
+ pci_read_config_word(pdev, pos + PCI_EXP_RTCTL, &rtctl);
+ if (enable)
+ rtctl |= PCI_EXP_RTCTL_PMEIE;
+ else
+ rtctl &= ~PCI_EXP_RTCTL_PMEIE;
+ pci_write_config_word(pdev, pos + PCI_EXP_RTCTL, rtctl);
+}
+
+/*
+ * pcie_pme_clear_status - clear root port PME report status
+ * @pdev: the root port
+ */
+static inline void pcie_pme_clear_status(struct pci_dev *pdev)
+{
+ int pos;
+ u32 rtsta;
+
+ pos = pci_find_capability(pdev, PCI_CAP_ID_EXP);
+
+ pci_read_config_dword(pdev, pos + PCI_EXP_RTSTA, &rtsta);
+ rtsta |= PCI_EXP_RTSTA_PME;
+ pci_write_config_dword(pdev, pos + PCI_EXP_RTSTA, rtsta);
+}
+
+/*
+ * pcie_pme_do_wakeup_event - handle PCIe native PME event
+ * @target is suspected to invoke a wakeup event (PME)
+ * Note @target should be a PCIe device
+ */
+static bool pcie_pme_do_wakeup_event(struct pci_dev *target)
+{
+ bool ret;
+ struct pci_dev *tmp = NULL;
+ int domain_nr, bus_start, bus_end;
+
+ BUG_ON(!target->is_pcie);
+
+ /* For PCIe PME, if the target isn't the source, do nothing */
+ ret = pci_pm_check_wakeup_event(target);
+
+ if (!target->subordinate || (target->pcie_type != PCI_EXP_TYPE_ROOT_PORT
+ && target->pcie_type != PCI_EXP_TYPE_PCI_BRIDGE)) {
+ if (ret)
+ pm_request_resume(&target->dev);
+ return ret;
+ }
+
+ if (ret)
+ pm_request_resume(&target->dev);
+
+ /* scan legacy PCI devices under the bridge or root port */
+ domain_nr = pci_domain_nr(target->bus);
+ bus_start = target->subordinate->secondary;
+ bus_end = target->subordinate->subordinate;
+ while ((tmp = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, tmp)) != NULL) {
+ if (pci_domain_nr(tmp->bus) != domain_nr ||
+ tmp->bus->number < bus_start ||
+ tmp->bus->number > bus_end)
+ continue;
+ if (!tmp->is_pcie && pci_pm_check_wakeup_event(tmp)) {
+ ret = true;
+ pm_request_resume(&tmp->dev);
+ }
+ }
+ return ret;
+}
+
+/*
+ * pcie_pme_handle_request - handle one PCI device's PME
+ * @root_port: the root port which gets PME report
+ * @requester_id: the device ID of PCI device which invokes PME event
+ */
+static void pcie_pme_handle_request(struct pci_dev *root_port,
+ u16 requester_id)
+{
+ struct pci_dev *target;
+ bool device_found = false;
+ u8 busnr = requester_id >> 8, devfn = requester_id & 0xff;
+
+ target = pci_get_bus_and_slot(busnr, devfn);
+
+ /* the PCIe-to-PCI bridge case, see below comment */
+ if (target && !target->is_pcie) {
+ pci_dev_put(target);
+ target = NULL;
+ printk(KERN_WARNING"%s: devfn should be zero\n", __func__);
+ devfn = 0; /* devfn should be zero */
+ }
+
+ if (target) {
+ device_found = pcie_pme_do_wakeup_event(target);
+ pci_dev_put(target);
+ goto out;
+ }
+
+ /*
+ * PME from PCI devices under a PCIe-to-PCI bridge may be converted to
+ * a PCIe in-band PME message. In such case, bridge will assign the
+ * message a new request id using bridge's secondary bus numer and
+ * device number/function number 0
+ */
+ if (!target && !devfn) {
+ struct pci_bus *bus;
+
+ bus = pci_find_bus(pci_domain_nr(root_port->bus), busnr);
+ if (bus) {
+ target = bus->self;
+ if (!target->is_pcie ||
+ target->pcie_type != PCI_EXP_TYPE_PCI_BRIDGE)
+ target = NULL;
+ }
+ }
+
+ if (target)
+ device_found = pcie_pme_do_wakeup_event(target);
+
+out:
+ if (!device_found)
+ printk(KERN_ERR"%s: Spurious Native PME interrupt received\n",
+ pci_name(root_port));
+}
+
+/* pcie_pme_work_handle - work handler for root port PME report interrupt */
+static void pcie_pme_work_handle(struct work_struct *work)
+{
+ struct pcie_pme_service_data *data =
+ container_of(work, struct pcie_pme_service_data, work);
+ struct pci_dev *root_port = data->dev->port;
+ unsigned long flags;
+ u32 rtsta;
+ int pos;
+
+ pos = pci_find_capability(root_port, PCI_CAP_ID_EXP);
+ while (1) {
+ spin_lock_irqsave(&data->lock, flags);
+ pci_read_config_dword(root_port, pos + PCI_EXP_RTSTA, &rtsta);
+
+ if (!(rtsta & PCI_EXP_RTSTA_PME)) {
+ spin_unlock_irqrestore(&data->lock, flags);
+
+ /* Has pending bit set but no status bit set, slow HW? */
+ if (rtsta & PCI_EXP_RTSTA_PEND) {
+ cpu_relax();
+ continue;
+ } else
+ break;
+ }
+
+ /* clear PME, if there are other pending PME, the status will
+ * get set again
+ */
+ pcie_pme_clear_status(root_port);
+ spin_unlock_irqrestore(&data->lock, flags);
+
+ pcie_pme_handle_request(root_port, rtsta & 0xffff);
+ }
+
+ spin_lock_irqsave(&data->lock, flags);
+ if (!data->in_exiting)
+ /* reenable native PME */
+ pcie_pme_enable_interrupt(root_port, true);
+ spin_unlock_irqrestore(&data->lock, flags);
+}
+
+/* pcie_pme_irq - interrupt handler for root port PME report interrupt */
+static irqreturn_t pcie_pme_irq(int irq, void *context)
+{
+ int pos;
+ struct pci_dev *pdev;
+ u32 rtsta;
+ struct pcie_pme_service_data *data;
+ unsigned long flags;
+
+ pdev = ((struct pcie_device *)context)->port;
+ data = get_service_data((struct pcie_device *)context);
+
+ pos = pci_find_capability(pdev, PCI_CAP_ID_EXP);
+
+ spin_lock_irqsave(&data->lock, flags);
+ pci_read_config_dword(pdev, pos + PCI_EXP_RTSTA, &rtsta);
+
+ if (!(rtsta & PCI_EXP_RTSTA_PME)) {
+ spin_unlock_irqrestore(&data->lock, flags);
+ return IRQ_NONE;
+ }
+
+ /* disable PME to avoid interrupt flood */
+ pcie_pme_enable_interrupt(pdev, false);
+ spin_unlock_irqrestore(&data->lock, flags);
+
+ queue_work(pm_wq, &data->work);
+
+ return IRQ_HANDLED;
+}
+
+#ifdef CONFIG_ACPI
+/*
+ * pcie_pme_osc_setup - declare OS support PCIe native PME feature to BIOS
+ * @pciedev - pcie device struct for root port
+ */
+static int pcie_pme_osc_setup(struct pcie_device *pciedev)
+{
+ acpi_status status = AE_NOT_FOUND;
+ struct pci_dev *pdev = pciedev->port;
+ acpi_handle handle = DEVICE_ACPI_HANDLE(&pdev->dev);
+
+ 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)) {
+ printk(KERN_DEBUG "Native PME service couldn't init device "
+ "%s - %s\n", dev_name(&pciedev->device),
+ (status == AE_SUPPORT || status == AE_NOT_FOUND) ?
+ "no _OSC support" : "Run ACPI _OSC fails");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+#else
+static inline int pcie_pme_osc_setup(struct pcie_device *pciedev)
+{
+ return 0;
+}
+#endif
+
+/*
+ * pcie_pme_probe - initialize PCIe PME report for a root port
+ * @dev - pcie device struct for root port
+ */
+static int pcie_pme_probe(struct pcie_device *dev)
+{
+ struct pci_dev *pdev;
+ int status;
+ struct pcie_pme_service_data *data;
+
+ if (pcie_pme_osc_setup(dev) && !force)
+ return -EINVAL;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+ spin_lock_init(&data->lock);
+ INIT_WORK(&data->work, pcie_pme_work_handle);
+ data->dev = dev;
+ set_service_data(dev, data);
+
+ pdev = dev->port;
+
+ /* clear pending PME */
+ pcie_pme_clear_status(pdev);
+
+ status = request_irq(dev->irq, pcie_pme_irq, IRQF_SHARED,
+ "pcie_pme", dev);
+ if (status) {
+ kfree(data);
+ return status;
+ }
+
+ /* enable PME interrupt */
+ pcie_pme_enable_interrupt(pdev, true);
+
+ return 0;
+}
+
+/*
+ * pcie_pme_remove - finialize PCIe PME report for a root port
+ * @dev - pcie device struct for root port
+ */
+static void pcie_pme_remove(struct pcie_device *dev)
+{
+ struct pci_dev *pdev;
+ unsigned long flags;
+ struct pcie_pme_service_data *data = get_service_data(dev);
+
+ pdev = dev->port;
+
+ /* disable PME interrupt */
+ spin_lock_irqsave(&data->lock, flags);
+ data->in_exiting = true;
+ pcie_pme_enable_interrupt(pdev, false);
+ spin_unlock_irqrestore(&data->lock, flags);
+
+ flush_scheduled_work();
+ free_irq(dev->irq, dev);
+
+ /* clear pending PME */
+ pcie_pme_clear_status(pdev);
+
+ set_service_data(dev, NULL);
+ kfree(data);
+}
+
+/*
+ * pcie_pme_suspend - suspend PCIe PME report for a root port
+ * @dev - pcie device struct for root port
+ */
+static int pcie_pme_suspend(struct pcie_device *dev)
+{
+ struct pci_dev *pdev;
+ struct pcie_pme_service_data *data;
+ unsigned long flags;
+
+ pdev = dev->port;
+ data = get_service_data(dev);
+
+ spin_lock_irqsave(&data->lock, flags);
+ /* disable PME to avoid further interrupt */
+ pcie_pme_enable_interrupt(pdev, false);
+
+ /* clear pending PME */
+ pcie_pme_clear_status(pdev);
+ spin_unlock_irqrestore(&data->lock, flags);
+
+ return 0;
+}
+
+/*
+ * pcie_pme_resume - resume PCIe PME report for a root port
+ * @dev - pcie device struct for root port
+ */
+static int pcie_pme_resume(struct pcie_device *dev)
+{
+ struct pci_dev *pdev = dev->port;
+ unsigned long flags;
+ struct pcie_pme_service_data *data = get_service_data(dev);
+
+ spin_lock_irqsave(&data->lock, flags);
+ pcie_pme_clear_status(pdev);
+ pcie_pme_enable_interrupt(pdev, true);
+ spin_unlock_irqrestore(&data->lock, flags);
+
+ return 0;
+}
+
+static struct pcie_port_service_driver pcie_pme_driver = {
+ .name = "pcie_pme",
+ .port_type = PCIE_RC_PORT,
+ .service = PCIE_PORT_SERVICE_PME,
+
+ .probe = pcie_pme_probe,
+ .remove = pcie_pme_remove,
+ .suspend = pcie_pme_suspend,
+ .resume = pcie_pme_resume,
+};
+
+static int __init pcie_pme_service_init(void)
+{
+ if (disabled)
+ return -EINVAL;
+ return pcie_port_service_register(&pcie_pme_driver);
+}
+
+static void __exit pcie_pme_service_exit(void)
+{
+ pcie_port_service_unregister(&pcie_pme_driver);
+}
+
+module_init(pcie_pme_service_init);
+module_exit(pcie_pme_service_exit);