From patchwork Thu Apr 14 10:04:28 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mika Westerberg X-Patchwork-Id: 8833771 X-Patchwork-Delegate: bhelgaas@google.com Return-Path: X-Original-To: patchwork-linux-pci@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork2.web.kernel.org (Postfix) with ESMTP id 55BD8C0553 for ; Thu, 14 Apr 2016 10:04:59 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 434FE2026F for ; Thu, 14 Apr 2016 10:04:58 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 066A8201BB for ; Thu, 14 Apr 2016 10:04:57 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753384AbcDNKEz (ORCPT ); Thu, 14 Apr 2016 06:04:55 -0400 Received: from mga09.intel.com ([134.134.136.24]:15889 "EHLO mga09.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753003AbcDNKEy (ORCPT ); Thu, 14 Apr 2016 06:04:54 -0400 Received: from fmsmga002.fm.intel.com ([10.253.24.26]) by orsmga102.jf.intel.com with ESMTP; 14 Apr 2016 03:04:33 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.24,484,1455004800"; d="scan'208";a="958502935" Received: from black.fi.intel.com ([10.237.72.93]) by fmsmga002.fm.intel.com with ESMTP; 14 Apr 2016 03:04:30 -0700 Received: by black.fi.intel.com (Postfix, from userid 1001) id C2524802; Thu, 14 Apr 2016 13:04:28 +0300 (EEST) From: Mika Westerberg To: Bjorn Helgaas , "Rafael J. Wysocki" Cc: Qipeng Zha , Qi Zheng , Dave Airlie , Mathias Nyman , Greg Kroah-Hartman , Lukas Wunner , Andreas Noever , Mika Westerberg , linux-pci@vger.kernel.org, linux-pm@vger.kernel.org Subject: [PATCH v3 4/4] PCI: Add runtime PM support for PCIe ports Date: Thu, 14 Apr 2016 13:04:28 +0300 Message-Id: <1460628268-16204-5-git-send-email-mika.westerberg@linux.intel.com> X-Mailer: git-send-email 2.8.0.rc3 In-Reply-To: <1460628268-16204-1-git-send-email-mika.westerberg@linux.intel.com> References: <1460628268-16204-1-git-send-email-mika.westerberg@linux.intel.com> Sender: linux-pci-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-pci@vger.kernel.org X-Spam-Status: No, score=-7.9 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Add back runtime PM support for PCIe ports that was removed in commit fe9a743a2601 ("PCI/PM: Drop unused runtime PM support code for PCIe ports"). First of all we cannot enable it automatically for all ports since there has been problems previously as can be seen in [1]. In summary suspended PCIe ports were not able to deal with ACPI based hotplug reliably. One reason why this might happen is the fact that when a PCIe port is powered down, config space access to the devices behind the port is not possible. If the BIOS hotplug SMI handler assumes the port is always in D0 it will not be able to find the hotplugged devices. To be on the safe side only enable runtime PM if the port does not claim to use ACPI based hotplug. Ports using native PCIe hotplug are not restricted by this. Furthermore we need to check that the PCI core thinks the port can go to D3 in the first place. The PCI core sets 'bridge_d3' in that case. If both conditions are met we enable and allow runtime PM for the PCIe port. Since 'bridge_d3' can be changed anytime we check this in driver ->runtime_idle() and ->runtime_suspend() and only allow runtime suspend if the flag is still set. The actual power transition to D3 and back is handled in the PCI core. Idea to automatically unblock (allow) runtime PM for PCIe ports came from Dave Airlie. [1] https://bugzilla.kernel.org/show_bug.cgi?id=53811 Signed-off-by: Mika Westerberg --- drivers/pci/pcie/portdrv_core.c | 2 + drivers/pci/pcie/portdrv_pci.c | 107 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/drivers/pci/pcie/portdrv_core.c b/drivers/pci/pcie/portdrv_core.c index 88122dc2e1b1..65b1a624826b 100644 --- a/drivers/pci/pcie/portdrv_core.c +++ b/drivers/pci/pcie/portdrv_core.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -343,6 +344,7 @@ static int pcie_device_init(struct pci_dev *pdev, int service, int irq) get_descriptor_id(pci_pcie_type(pdev), service)); device->parent = &pdev->dev; device_enable_async_suspend(device); + pm_runtime_no_callbacks(device); retval = device_register(device); if (retval) { diff --git a/drivers/pci/pcie/portdrv_pci.c b/drivers/pci/pcie/portdrv_pci.c index 6c6bb03392ea..83150650570e 100644 --- a/drivers/pci/pcie/portdrv_pci.c +++ b/drivers/pci/pcie/portdrv_pci.c @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include "portdrv.h" #include "aer/aerdrv.h" @@ -58,6 +60,10 @@ __setup("pcie_ports=", pcie_port_setup); /* global data */ +struct pcie_port_data { + bool runtime_pm_enabled; +}; + /** * pcie_clear_root_pme_status - Clear root port PME interrupt status. * @dev: PCIe root port or event collector. @@ -93,6 +99,78 @@ static int pcie_port_resume_noirq(struct device *dev) return 0; } +static int pcie_port_runtime_suspend(struct device *dev) +{ + return to_pci_dev(dev)->bridge_d3 ? 0 : -EBUSY; +} + +static int pcie_port_runtime_resume(struct device *dev) +{ + return 0; +} + +static int pcie_port_runtime_idle(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + + /* + * Rely the PCI core has set bridge_d3 whenever it thinks the port + * should be good to go to D3. Everything else, including moving + * the port to D3, is handled by the PCI core. + */ + if (pdev->bridge_d3) { + pm_schedule_suspend(dev, 10); + return 0; + } + return -EBUSY; +} + +#ifdef CONFIG_ACPI +static bool pcie_port_uses_acpi_hotplug(struct pci_dev *pdev) +{ + const struct acpi_pci_root *root; + acpi_handle handle; + + if (!pdev->is_hotplug_bridge) + return false; + + handle = acpi_find_root_bridge_handle(pdev); + if (!handle) + return false; + + root = acpi_pci_find_root(handle); + if (!root) + return false; + + return !(root->osc_control_set & OSC_PCI_EXPRESS_NATIVE_HP_CONTROL); +} +#else +static inline bool pcie_port_uses_acpi_hotplug(struct pci_dev *pdev) +{ + return false; +} +#endif + +static bool pcie_port_runtime_pm_possible(struct pci_dev *pdev) +{ + /* + * Only enable runtime PM if the PCI core agrees that this port can + * even go to D3. + */ + if (!pdev->bridge_d3) + return false; + + /* + * Prevent runtime PM if the port is using ACPI based hotplug. + * Otherwise the BIOS hotplug SMI code might not be able to + * enumerate devices behind this port properly (the port is powered + * down preventing all config space accesses to the subordinate + * devices). Native hotplug is expected to work with runtime PM + * enabled. + */ + return !pcie_port_uses_acpi_hotplug(pdev); +} + static const struct dev_pm_ops pcie_portdrv_pm_ops = { .suspend = pcie_port_device_suspend, .resume = pcie_port_device_resume, @@ -101,12 +179,20 @@ static const struct dev_pm_ops pcie_portdrv_pm_ops = { .poweroff = pcie_port_device_suspend, .restore = pcie_port_device_resume, .resume_noirq = pcie_port_resume_noirq, + .runtime_suspend = pcie_port_runtime_suspend, + .runtime_resume = pcie_port_runtime_resume, + .runtime_idle = pcie_port_runtime_idle, }; #define PCIE_PORTDRV_PM_OPS (&pcie_portdrv_pm_ops) #else /* !PM */ +static inline bool pcie_port_runtime_pm_possible(struct pci_dev *pdev) +{ + return false; +} + #define PCIE_PORTDRV_PM_OPS NULL #endif /* !PM */ @@ -121,6 +207,7 @@ static const struct dev_pm_ops pcie_portdrv_pm_ops = { static int pcie_portdrv_probe(struct pci_dev *dev, const struct pci_device_id *id) { + struct pcie_port_data *pdata; int status; if (!pci_is_pcie(dev) || @@ -134,11 +221,31 @@ static int pcie_portdrv_probe(struct pci_dev *dev, return status; pci_save_state(dev); + + pdata = devm_kzalloc(&dev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pci_set_drvdata(dev, pdata); + if (pcie_port_runtime_pm_possible(dev)) { + pm_runtime_put_noidle(&dev->dev); + pm_runtime_allow(&dev->dev); + + pdata->runtime_pm_enabled = true; + } + return 0; } static void pcie_portdrv_remove(struct pci_dev *dev) { + const struct pcie_port_data *pdata = pci_get_drvdata(dev); + + if (pdata->runtime_pm_enabled) { + pm_runtime_forbid(&dev->dev); + pm_runtime_get_noresume(&dev->dev); + } + pcie_port_device_remove(dev); }