lkml.org 
[lkml]   [2009]   [Jan]   [23]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    Date
    From
    Subject[patch 18/40] PCI: keep ASPM link state consistent throughout PCIe hierarchy
    2.6.27-stable review patch.  If anyone has any objections, please let us know.

    ------------------

    From: Shaohua Li <shaohua.li@intel.com>

    commit 46bbdfa44cfc0d352148a0dc33ba9f6db02ccdf0 upstream.

    In a PCIe hierarchy with a switch present, if the link state of an
    endpoint device is changed, we must check the whole hierarchy from the
    endpoint device to root port, and for each link in the hierarchy, the new
    link state should be configured. Previously, the implementation checked
    the state but forgot to configure the links between root port to switch.
    Fixes Novell bz #448987.

    Signed-off-by: Shaohua Li <shaohua.li@intel.com>
    Tested-by: Andrew Patterson <andrew.patterson@hp.com>
    Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>
    Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

    ---
    drivers/pci/pcie/aspm.c | 127 ++++++++++++++++++++++++++++++++++++++++--------
    1 file changed, 107 insertions(+), 20 deletions(-)

    --- a/drivers/pci/pcie/aspm.c
    +++ b/drivers/pci/pcie/aspm.c
    @@ -33,6 +33,11 @@ struct endpoint_state {
    struct pcie_link_state {
    struct list_head sibiling;
    struct pci_dev *pdev;
    + bool downstream_has_switch;
    +
    + struct pcie_link_state *parent;
    + struct list_head children;
    + struct list_head link;

    /* ASPM state */
    unsigned int support_state;
    @@ -125,7 +130,7 @@ static void pcie_set_clock_pm(struct pci
    link_state->clk_pm_enabled = !!enable;
    }

    -static void pcie_check_clock_pm(struct pci_dev *pdev)
    +static void pcie_check_clock_pm(struct pci_dev *pdev, int blacklist)
    {
    int pos;
    u32 reg32;
    @@ -149,10 +154,26 @@ static void pcie_check_clock_pm(struct p
    if (!(reg16 & PCI_EXP_LNKCTL_CLKREQ_EN))
    enabled = 0;
    }
    - link_state->clk_pm_capable = capable;
    link_state->clk_pm_enabled = enabled;
    link_state->bios_clk_state = enabled;
    - pcie_set_clock_pm(pdev, policy_to_clkpm_state(pdev));
    + if (!blacklist) {
    + link_state->clk_pm_capable = capable;
    + pcie_set_clock_pm(pdev, policy_to_clkpm_state(pdev));
    + } else {
    + link_state->clk_pm_capable = 0;
    + pcie_set_clock_pm(pdev, 0);
    + }
    +}
    +
    +static bool pcie_aspm_downstream_has_switch(struct pci_dev *pdev)
    +{
    + struct pci_dev *child_dev;
    +
    + list_for_each_entry(child_dev, &pdev->subordinate->devices, bus_list) {
    + if (child_dev->pcie_type == PCI_EXP_TYPE_UPSTREAM)
    + return true;
    + }
    + return false;
    }

    /*
    @@ -419,9 +440,9 @@ static unsigned int pcie_aspm_check_stat
    {
    struct pci_dev *child_dev;

    - /* If no child, disable the link */
    + /* If no child, ignore the link */
    if (list_empty(&pdev->subordinate->devices))
    - return 0;
    + return state;
    list_for_each_entry(child_dev, &pdev->subordinate->devices, bus_list) {
    if (child_dev->pcie_type == PCI_EXP_TYPE_PCI_BRIDGE) {
    /*
    @@ -462,6 +483,9 @@ static void __pcie_aspm_config_link(stru
    int valid = 1;
    struct pcie_link_state *link_state = pdev->link_state;

    + /* If no child, disable the link */
    + if (list_empty(&pdev->subordinate->devices))
    + state = 0;
    /*
    * if the downstream component has pci bridge function, don't do ASPM
    * now
    @@ -493,20 +517,52 @@ static void __pcie_aspm_config_link(stru
    link_state->enabled_state = state;
    }

    +static struct pcie_link_state *get_root_port_link(struct pcie_link_state *link)
    +{
    + struct pcie_link_state *root_port_link = link;
    + while (root_port_link->parent)
    + root_port_link = root_port_link->parent;
    + return root_port_link;
    +}
    +
    +/* check the whole hierarchy, and configure each link in the hierarchy */
    static void __pcie_aspm_configure_link_state(struct pci_dev *pdev,
    unsigned int state)
    {
    struct pcie_link_state *link_state = pdev->link_state;
    + struct pcie_link_state *root_port_link = get_root_port_link(link_state);
    + struct pcie_link_state *leaf;

    - if (link_state->support_state == 0)
    - return;
    state &= PCIE_LINK_STATE_L0S|PCIE_LINK_STATE_L1;

    - /* state 0 means disabling aspm */
    - state = pcie_aspm_check_state(pdev, state);
    + /* check all links who have specific root port link */
    + list_for_each_entry(leaf, &link_list, sibiling) {
    + if (!list_empty(&leaf->children) ||
    + get_root_port_link(leaf) != root_port_link)
    + continue;
    + state = pcie_aspm_check_state(leaf->pdev, state);
    + }
    + /* check root port link too in case it hasn't children */
    + state = pcie_aspm_check_state(root_port_link->pdev, state);
    +
    if (link_state->enabled_state == state)
    return;
    - __pcie_aspm_config_link(pdev, state);
    +
    + /*
    + * we must change the hierarchy. See comments in
    + * __pcie_aspm_config_link for the order
    + **/
    + if (state & PCIE_LINK_STATE_L1) {
    + list_for_each_entry(leaf, &link_list, sibiling) {
    + if (get_root_port_link(leaf) == root_port_link)
    + __pcie_aspm_config_link(leaf->pdev, state);
    + }
    + } else {
    + list_for_each_entry_reverse(leaf, &link_list, sibiling) {
    + if (get_root_port_link(leaf) == root_port_link)
    + __pcie_aspm_config_link(leaf->pdev, state);
    + }
    + }
    }

    /*
    @@ -570,6 +626,7 @@ void pcie_aspm_init_link_state(struct pc
    unsigned int state;
    struct pcie_link_state *link_state;
    int error = 0;
    + int blacklist;

    if (aspm_disabled || !pdev->is_pcie || pdev->link_state)
    return;
    @@ -580,29 +637,58 @@ void pcie_aspm_init_link_state(struct pc
    if (list_empty(&pdev->subordinate->devices))
    goto out;

    - if (pcie_aspm_sanity_check(pdev))
    - goto out;
    + blacklist = !!pcie_aspm_sanity_check(pdev);

    mutex_lock(&aspm_lock);

    link_state = kzalloc(sizeof(*link_state), GFP_KERNEL);
    if (!link_state)
    goto unlock_out;
    - pdev->link_state = link_state;

    - pcie_aspm_configure_common_clock(pdev);
    -
    - pcie_aspm_cap_init(pdev);
    + link_state->downstream_has_switch = pcie_aspm_downstream_has_switch(pdev);
    + INIT_LIST_HEAD(&link_state->children);
    + INIT_LIST_HEAD(&link_state->link);
    + if (pdev->bus->self) {/* this is a switch */
    + struct pcie_link_state *parent_link_state;
    +
    + parent_link_state = pdev->bus->parent->self->link_state;
    + if (!parent_link_state) {
    + kfree(link_state);
    + goto unlock_out;
    + }
    + list_add(&link_state->link, &parent_link_state->children);
    + link_state->parent = parent_link_state;
    + }

    - /* config link state to avoid BIOS error */
    - state = pcie_aspm_check_state(pdev, policy_to_aspm_state(pdev));
    - __pcie_aspm_config_link(pdev, state);
    + pdev->link_state = link_state;

    - pcie_check_clock_pm(pdev);
    + if (!blacklist) {
    + pcie_aspm_configure_common_clock(pdev);
    + pcie_aspm_cap_init(pdev);
    + } else {
    + link_state->enabled_state = PCIE_LINK_STATE_L0S|PCIE_LINK_STATE_L1;
    + link_state->bios_aspm_state = 0;
    + /* Set support state to 0, so we will disable ASPM later */
    + link_state->support_state = 0;
    + }

    link_state->pdev = pdev;
    list_add(&link_state->sibiling, &link_list);

    + if (link_state->downstream_has_switch) {
    + /*
    + * If link has switch, delay the link config. The leaf link
    + * initialization will config the whole hierarchy. but we must
    + * make sure BIOS doesn't set unsupported link state
    + **/
    + state = pcie_aspm_check_state(pdev, link_state->bios_aspm_state);
    + __pcie_aspm_config_link(pdev, state);
    + } else
    + __pcie_aspm_configure_link_state(pdev,
    + policy_to_aspm_state(pdev));
    +
    + pcie_check_clock_pm(pdev, blacklist);
    +
    unlock_out:
    if (error)
    free_link_state(pdev);
    @@ -635,6 +721,7 @@ void pcie_aspm_exit_link_state(struct pc
    /* All functions are removed, so just disable ASPM for the link */
    __pcie_aspm_config_one_dev(parent, 0);
    list_del(&link_state->sibiling);
    + list_del(&link_state->link);
    /* Clock PM is for endpoint device */

    free_link_state(parent);


    \
     
     \ /
      Last update: 2009-01-23 07:27    [W:0.031 / U:119.636 seconds]
    ©2003-2016 Jasper Spaans. hosted at Digital OceanAdvertise on this site