[PATCH V3] PCI/ASPM: reconfigure ASPM following hotplug for POLICY_DEFAULT
Bjorn Helgaas
helgaas at kernel.org
Thu Mar 9 14:27:51 PST 2017
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 at codeaurora.org>
> Signed-off-by: Mayurkumar Patel <mayurkumar.patel at 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 at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
More information about the linux-arm-kernel
mailing list