diff mbox

[V3] PCI/ASPM: reconfigure ASPM following hotplug for POLICY_DEFAULT

Message ID 1489005551-23598-1-git-send-email-okaya@codeaurora.org (mailing list archive)
State New, archived
Delegated to: Bjorn Helgaas
Headers show

Commit Message

Sinan Kaya March 8, 2017, 8:39 p.m. UTC
When the operating system is booted with the default ASPM policy
(POLICY_DEFAULT), current code is querying the enable/disable
states from ASPM registers to determine the policy.

For example, a BIOS could set the power saving state to performance
and clear all ASPM control registers. A balanced ASPM policy could
enable L0s and disable L1. A power conscious BIOS could enable both
L0s and L1 to trade off latency and performance vs. power.

After hotplug removal, pcie_aspm_exit_link_state() function clears
the ASPM registers. An insertion following hotplug removal reads
incorrect policy as ASPM disabled even though ASPM was enabled
during boot.

This is caused by the fact that same function is used for reconfiguring
ASPM regardless of the of the power on state.

This patch builds some state info into pcie_aspm_cap_init() function.

Adding a flag and a counter to the struct pci_dev to save the
power up policy in the bridge during boot. ASPM enable counter is
used as a switch to determine when to use saved value.

Signed-off-by: Sinan Kaya <okaya@codeaurora.org>
Signed-off-by: Mayurkumar Patel <mayurkumar.patel@intel.com>
---
 drivers/pci/pcie/aspm.c | 21 +++++++++++++++++----
 include/linux/pci.h     |  2 ++
 2 files changed, 19 insertions(+), 4 deletions(-)

Comments

Bjorn Helgaas March 9, 2017, 10:27 p.m. UTC | #1
Hi Sinan,

On Wed, Mar 08, 2017 at 03:39:11PM -0500, Sinan Kaya wrote:
> When the operating system is booted with the default ASPM policy
> (POLICY_DEFAULT), current code is querying the enable/disable
> states from ASPM registers to determine the policy.
> 
> For example, a BIOS could set the power saving state to performance
> and clear all ASPM control registers. A balanced ASPM policy could
> enable L0s and disable L1. A power conscious BIOS could enable both
> L0s and L1 to trade off latency and performance vs. power.
> 
> After hotplug removal, pcie_aspm_exit_link_state() function clears
> the ASPM registers. An insertion following hotplug removal reads
> incorrect policy as ASPM disabled even though ASPM was enabled
> during boot.
> 
> This is caused by the fact that same function is used for reconfiguring
> ASPM regardless of the of the power on state.
> 
> This patch builds some state info into pcie_aspm_cap_init() function.
> 
> Adding a flag and a counter to the struct pci_dev to save the
> power up policy in the bridge during boot. ASPM enable counter is
> used as a switch to determine when to use saved value.
> 
> Signed-off-by: Sinan Kaya <okaya@codeaurora.org>
> Signed-off-by: Mayurkumar Patel <mayurkumar.patel@intel.com>
> ---
>  drivers/pci/pcie/aspm.c | 21 +++++++++++++++++----
>  include/linux/pci.h     |  2 ++
>  2 files changed, 19 insertions(+), 4 deletions(-)
> 
> diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c
> index 17ac1dc..63435b0 100644
> --- a/drivers/pci/pcie/aspm.c
> +++ b/drivers/pci/pcie/aspm.c
> @@ -338,8 +338,9 @@ static void pcie_aspm_check_latency(struct pci_dev *endpoint)
>  	}
>  }
>  
> -static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist)
> +static void pcie_aspm_cap_init(struct pci_dev *pdev, int blacklist)
>  {
> +	struct pcie_link_state *link = pdev->link_state;
>  	struct pci_dev *child, *parent = link->pdev;
>  	struct pci_bus *linkbus = parent->subordinate;
>  	struct aspm_register_info upreg, dwreg;
> @@ -397,8 +398,20 @@ static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist)
>  	link->latency_up.l1 = calc_l1_latency(upreg.latency_encoding_l1);
>  	link->latency_dw.l1 = calc_l1_latency(dwreg.latency_encoding_l1);
>  
> -	/* Save default state */
> -	link->aspm_default = link->aspm_enabled;
> +	/*
> +	 * Save default state from FW when enabling ASPM for the first time
> +	 * during boot by looking at the calculated link->aspm_enabled bits
> +	 * above and aspm_enable_cnt will be zero.
> +	 *
> +	 * If this path is getting called for the second/third time
> +	 * (aspm_enable_cnt will be non-zero). Assume that the current state
> +	 * of the ASPM registers may not necessarily match what FW asked us to
> +	 * do as in the case of hotplug insertion/removal.
> +	 */
> +	if (atomic_inc_return(&pdev->aspm_enable_cnt) == 1)
> +		pdev->aspm_default = link->aspm_default = link->aspm_enabled;
> +	else
> +		link->aspm_default = pdev->aspm_default;

This is an aspect of the ASPM design that I don't like:

  - pcie_aspm_init_link_state() is called on a *bridge* after we've
    enumerated any devices below the bridge, and we allocate the
    link_state.

  - pcie_aspm_exit_link_state() is called on an *endpoint*, and if
    we're removing the last endpoint below a bridge, we release the
    *parent's* link_state.

This leads to the problem you're trying to solve here: we throw away
all the bridge's ASPM information when the child is removed, so we
lose the original settings from firmware.

Your solution is to add some state outside the link_state structure,
and initialize it the first time we enable ASPM for the link.  With
the current design, you don't really have much choice, but I think it
ends up being more complicated than it should be.

How hard do you think it would be to rework this path slightly so we:

  - call pcie_aspm_init_link_state() for every device, maybe from
    pci_init_capabilities()

  - for bridges, have pcie_aspm_init_link_state() allocate a
    link_state, regardless of whether it currently has any children,
    and save the ASPM settings done by firmware

  - for endpoints, have pcie_aspm_init_link_state() do the actual ASPM
    setup of the link as it currently does

  - for endpoints, change pcie_aspm_exit_link_state() so it cleans up
    the device's own state and disables ASPM if necessary, but doesn't
    remove the parent's link_state

  - for bridges, change pcie_aspm_exit_link_state() so it frees the
    bridge's own link_state

?

>  	/* Setup initial capable state. Will be updated later */
>  	link->aspm_capable = link->aspm_support;
> @@ -599,7 +612,7 @@ void pcie_aspm_init_link_state(struct pci_dev *pdev)
>  	 * upstream links also because capable state of them can be
>  	 * update through pcie_aspm_cap_init().
>  	 */
> -	pcie_aspm_cap_init(link, blacklist);
> +	pcie_aspm_cap_init(pdev, blacklist);
>  
>  	/* Setup initial Clock PM state */
>  	pcie_clkpm_cap_init(link, blacklist);
> diff --git a/include/linux/pci.h b/include/linux/pci.h
> index e2d1a12..aa7bd7e 100644
> --- a/include/linux/pci.h
> +++ b/include/linux/pci.h
> @@ -321,6 +321,8 @@ struct pci_dev {
>  
>  #ifdef CONFIG_PCIEASPM
>  	struct pcie_link_state	*link_state;	/* ASPM link state */
> +	unsigned int	aspm_default;		/* ASPM policy set by BIOS */
> +	atomic_t	aspm_enable_cnt;	/* ASPM policy initialization */
>  #endif
>  
>  	pci_channel_state_t error_state;	/* current connectivity state */
> -- 
> 1.9.1
> 
> 
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
Sinan Kaya March 10, 2017, 2:36 p.m. UTC | #2
Hi Bjorn,

On 3/9/2017 5:27 PM, Bjorn Helgaas wrote:
> How hard do you think it would be to rework this path slightly so we:
> 
>   - call pcie_aspm_init_link_state() for every device, maybe from
>     pci_init_capabilities()
> 
>   - for bridges, have pcie_aspm_init_link_state() allocate a
>     link_state, regardless of whether it currently has any children,
>     and save the ASPM settings done by firmware
> 
>   - for endpoints, have pcie_aspm_init_link_state() do the actual ASPM
>     setup of the link as it currently does
> 
>   - for endpoints, change pcie_aspm_exit_link_state() so it cleans up
>     the device's own state and disables ASPM if necessary, but doesn't
>     remove the parent's link_state
> 
>   - for bridges, change pcie_aspm_exit_link_state() so it frees the
>     bridge's own link_state

Thanks for the feedback, Let me take a stab at this. 
Sinan
Patel, Mayurkumar March 13, 2017, 8:49 a.m. UTC | #3
>
>Hi Bjorn,
>
>On 3/9/2017 5:27 PM, Bjorn Helgaas wrote:
>> How hard do you think it would be to rework this path slightly so we:
>>
>>   - call pcie_aspm_init_link_state() for every device, maybe from
>>     pci_init_capabilities()
>>
>>   - for bridges, have pcie_aspm_init_link_state() allocate a
>>     link_state, regardless of whether it currently has any children,
>>     and save the ASPM settings done by firmware
>>
>>   - for endpoints, have pcie_aspm_init_link_state() do the actual ASPM
>>     setup of the link as it currently does
>>
>>   - for endpoints, change pcie_aspm_exit_link_state() so it cleans up
>>     the device's own state and disables ASPM if necessary, but doesn't
>>     remove the parent's link_state
>>
>>   - for bridges, change pcie_aspm_exit_link_state() so it frees the
>>     bridge's own link_state
>
>Thanks for the feedback, Let me take a stab at this.
>Sinan

Thanks Bjorn and Sinan. I will wait for new patch-sets from Sinan.

>
>--
>Sinan Kaya
>Qualcomm Datacenter Technologies, Inc. as an affiliate of Qualcomm Technologies, Inc.
>Qualcomm Technologies, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project.
Intel Deutschland GmbH
Registered Address: Am Campeon 10-12, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Christin Eisenschmid, Christian Lamprechter
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928
Sinan Kaya March 13, 2017, 9:06 p.m. UTC | #4
Hi Bjorn,

On 3/9/2017 5:27 PM, Bjorn Helgaas wrote:
> This is an aspect of the ASPM design that I don't like:
> 
>   - pcie_aspm_init_link_state() is called on a *bridge* after we've
>     enumerated any devices below the bridge, and we allocate the
>     link_state.
> 
>   - pcie_aspm_exit_link_state() is called on an *endpoint*, and if
>     we're removing the last endpoint below a bridge, we release the
>     *parent's* link_state.

I just posted V4 a minute ago. I divided ASPM init into two so that the
ASPM registers are captured in device_add path. 

There is a section of the ASPM init code that needs to walk all the devices
under the bridge. 

This section of the code is not working in device_add path as the link list
for child devices has not been set up yet. 

Unless you tell me there is a better way to handle this, I don't see how
we can remove the call from scan_slot. 

After further review, I think I can split PATCH V4 3/3 into two. Before
diving into too much clean up, I wanted to get some feedback as it is
obvious from the messed up cover letter.

Sinan
diff mbox

Patch

diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c
index 17ac1dc..63435b0 100644
--- a/drivers/pci/pcie/aspm.c
+++ b/drivers/pci/pcie/aspm.c
@@ -338,8 +338,9 @@  static void pcie_aspm_check_latency(struct pci_dev *endpoint)
 	}
 }
 
-static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist)
+static void pcie_aspm_cap_init(struct pci_dev *pdev, int blacklist)
 {
+	struct pcie_link_state *link = pdev->link_state;
 	struct pci_dev *child, *parent = link->pdev;
 	struct pci_bus *linkbus = parent->subordinate;
 	struct aspm_register_info upreg, dwreg;
@@ -397,8 +398,20 @@  static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist)
 	link->latency_up.l1 = calc_l1_latency(upreg.latency_encoding_l1);
 	link->latency_dw.l1 = calc_l1_latency(dwreg.latency_encoding_l1);
 
-	/* Save default state */
-	link->aspm_default = link->aspm_enabled;
+	/*
+	 * Save default state from FW when enabling ASPM for the first time
+	 * during boot by looking at the calculated link->aspm_enabled bits
+	 * above and aspm_enable_cnt will be zero.
+	 *
+	 * If this path is getting called for the second/third time
+	 * (aspm_enable_cnt will be non-zero). Assume that the current state
+	 * of the ASPM registers may not necessarily match what FW asked us to
+	 * do as in the case of hotplug insertion/removal.
+	 */
+	if (atomic_inc_return(&pdev->aspm_enable_cnt) == 1)
+		pdev->aspm_default = link->aspm_default = link->aspm_enabled;
+	else
+		link->aspm_default = pdev->aspm_default;
 
 	/* Setup initial capable state. Will be updated later */
 	link->aspm_capable = link->aspm_support;
@@ -599,7 +612,7 @@  void pcie_aspm_init_link_state(struct pci_dev *pdev)
 	 * upstream links also because capable state of them can be
 	 * update through pcie_aspm_cap_init().
 	 */
-	pcie_aspm_cap_init(link, blacklist);
+	pcie_aspm_cap_init(pdev, blacklist);
 
 	/* Setup initial Clock PM state */
 	pcie_clkpm_cap_init(link, blacklist);
diff --git a/include/linux/pci.h b/include/linux/pci.h
index e2d1a12..aa7bd7e 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -321,6 +321,8 @@  struct pci_dev {
 
 #ifdef CONFIG_PCIEASPM
 	struct pcie_link_state	*link_state;	/* ASPM link state */
+	unsigned int	aspm_default;		/* ASPM policy set by BIOS */
+	atomic_t	aspm_enable_cnt;	/* ASPM policy initialization */
 #endif
 
 	pci_channel_state_t error_state;	/* current connectivity state */