From patchwork Mon Feb 29 12:56:03 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mika Westerberg X-Patchwork-Id: 8453301 X-Patchwork-Delegate: bhelgaas@google.com Return-Path: X-Original-To: patchwork-linux-pci@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 04E439F38C for ; Mon, 29 Feb 2016 12:56:18 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 0703C20222 for ; Mon, 29 Feb 2016 12:56:17 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 41D66202BE for ; Mon, 29 Feb 2016 12:56:15 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751194AbcB2M4M (ORCPT ); Mon, 29 Feb 2016 07:56:12 -0500 Received: from mga04.intel.com ([192.55.52.120]:60693 "EHLO mga04.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1750861AbcB2M4L (ORCPT ); Mon, 29 Feb 2016 07:56:11 -0500 Received: from fmsmga001.fm.intel.com ([10.253.24.23]) by fmsmga104.fm.intel.com with ESMTP; 29 Feb 2016 04:56:10 -0800 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.22,520,1449561600"; d="scan'208";a="913492448" Received: from black.fi.intel.com ([10.237.72.93]) by fmsmga001.fm.intel.com with ESMTP; 29 Feb 2016 04:56:08 -0800 Received: by black.fi.intel.com (Postfix, from userid 1001) id 12A3615B; Mon, 29 Feb 2016 14:56:07 +0200 (EET) From: Mika Westerberg To: Bjorn Helgaas Cc: "Rafael J. Wysocki" , Qipeng Zha , Qi Zheng , Mika Westerberg , linux-pci@vger.kernel.org, linux-pm@vger.kernel.org Subject: [PATCH 3/6] PCI: Move PCIe ports to D3hot during suspend Date: Mon, 29 Feb 2016 14:56:03 +0200 Message-Id: <1456750566-116248-4-git-send-email-mika.westerberg@linux.intel.com> X-Mailer: git-send-email 2.7.0 In-Reply-To: <1456750566-116248-1-git-send-email-mika.westerberg@linux.intel.com> References: <1456750566-116248-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=-6.9 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=ham 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 Currently the PCI core does not do this automatically as it avoids changing power state for bridges and PCIe ports. With recent hardware PCIe ports can be moved to D3hot given that we take into account few restrictions: - Devices connected to the ports are effectively in D3cold once the root port is moved to D3hot (the config space is not accessible anymore and the link may be powered down). - The device needs to be able to transition to D3cold. - If the device is capable of waking the system it needs to be able to do so from D3cold (PME from D3cold). We assume all recent hardware (starting from 2015) is capable of doing this but make it possible to add exceptions via entries in pcie_port_configs[]. Signed-off-by: Mika Westerberg --- drivers/pci/pcie/portdrv_pci.c | 103 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 100 insertions(+), 3 deletions(-) diff --git a/drivers/pci/pcie/portdrv_pci.c b/drivers/pci/pcie/portdrv_pci.c index 6c6bb03392ea..fe3349685141 100644 --- a/drivers/pci/pcie/portdrv_pci.c +++ b/drivers/pci/pcie/portdrv_pci.c @@ -20,6 +20,7 @@ #include "portdrv.h" #include "aer/aerdrv.h" +#include "../pci.h" /* * Version Information @@ -78,11 +79,105 @@ static int pcie_portdrv_restore_config(struct pci_dev *dev) return 0; } +enum pcie_port_type { + PCIE_PORT_DEFAULT, +}; + +struct pcie_port_config { + bool suspend_allowed; +}; + +static const struct pcie_port_config pcie_port_configs[] = { + [PCIE_PORT_DEFAULT] = { + .suspend_allowed = true, + }, +}; + #ifdef CONFIG_PM +static const struct pcie_port_config *pcie_port_get_config(struct pci_dev *pdev) +{ + const struct pci_device_id *id = pci_match_id(pdev->driver->id_table, + pdev); + return &pcie_port_configs[id->driver_data]; +} + +static int pcie_port_check_d3cold(struct pci_dev *pdev, void *data) +{ + bool *d3cold_ok = data; + + if (pdev->no_d3cold || !pdev->d3cold_allowed) + *d3cold_ok = false; + if (device_may_wakeup(&pdev->dev) && !pci_pme_capable(pdev, PCI_D3cold)) + *d3cold_ok = false; + + return !*d3cold_ok; +} + +static bool pcie_port_can_suspend(struct pci_dev *pdev) +{ + bool d3cold_ok = true; + + /* + * When the port is put to D3hot the devices behind the port are + * effectively in D3cold as their config space cannot be accessed + * anymore and the link may be powered down. + * + * We only allow the port to go to D3hot the devices: + * - Are allowed to go to D3cold + * - Can wake up from D3cold if they are wake capable + */ + pci_walk_bus(pdev->subordinate, pcie_port_check_d3cold, &d3cold_ok); + return d3cold_ok; +} + +static bool pcie_port_suspend_allowed(struct pci_dev *pdev) +{ + const struct pcie_port_config *config = pcie_port_get_config(pdev); + + /* + * Older hardware is not capable of moving PCIe ports to D3 so + * anything earlier than 2015 is assumed not to support this. + */ + if (dmi_available) { + unsigned year; + + if (!dmi_get_date(DMI_BIOS_DATE, &year, NULL, NULL) || + year < 2015) { + return false; + } + } + + /* Per port configuration can forbid it as well */ + if (!config->suspend_allowed) + return false; + + return pcie_port_can_suspend(pdev); +} + +static int pcie_port_suspend_noirq(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + + if (pcie_port_suspend_allowed(pdev)) { + pci_save_state(pdev); + pci_set_power_state(pdev, PCI_D3hot); + /* + * All devices behind the port are assumed to be in D3cold + * so update their state now. + */ + __pci_bus_set_current_state(pdev->subordinate, PCI_D3cold); + } + + return 0; +} + static int pcie_port_resume_noirq(struct device *dev) { struct pci_dev *pdev = to_pci_dev(dev); + pci_set_power_state(pdev, PCI_D0); + pci_restore_state(pdev); + /* * Some BIOSes forget to clear Root PME Status bits after system wakeup * which breaks ACPI-based runtime wakeup on PCI Express, so clear those @@ -100,6 +195,7 @@ static const struct dev_pm_ops pcie_portdrv_pm_ops = { .thaw = pcie_port_device_resume, .poweroff = pcie_port_device_suspend, .restore = pcie_port_device_resume, + .suspend_noirq = pcie_port_suspend_noirq, .resume_noirq = pcie_port_resume_noirq, }; @@ -285,10 +381,11 @@ static void pcie_portdrv_err_resume(struct pci_dev *dev) /* * LINUX Device Driver Model */ -static const struct pci_device_id port_pci_ids[] = { { +static const struct pci_device_id port_pci_ids[] = { /* handle any PCI-Express port */ - PCI_DEVICE_CLASS(((PCI_CLASS_BRIDGE_PCI << 8) | 0x00), ~0), - }, { /* end: all zeroes */ } + { PCI_DEVICE_CLASS(((PCI_CLASS_BRIDGE_PCI << 8) | 0x00), ~0), + .driver_data = PCIE_PORT_DEFAULT }, + { /* end: all zeroes */ } }; MODULE_DEVICE_TABLE(pci, port_pci_ids);