PCI ASPM: introduce capable flag
authorKenji Kaneshige <kaneshige.kenji@jp.fujitsu.com>
Wed, 19 Aug 2009 02:00:25 +0000 (11:00 +0900)
committerJesse Barnes <jbarnes@virtuousgeek.org>
Wed, 9 Sep 2009 20:29:47 +0000 (13:29 -0700)
Introduce 'aspm_capable' field to maintain the capable ASPM setting of
the link. By the 'aspm_capable', we don't need to recheck latency
every time ASPM policy is changed.

Each bit in 'aspm_capable' is associated to ASPM state (L0S/L1). The
bit is set if the associated ASPM state is supported by the link and
it satisfies the latency requirement (i.e. exit latency < endpoint
acceptable latency). The 'aspm_capable' is updated when

  - an endpoint device is added (boot time or hot-plug time)
  - an endpoint device is removed (hot-unplug time)
  - PCI power state is changed.

Acked-by: Shaohua Li <shaohua.li@intel.com>
Signed-off-by: Kenji Kaneshige <kaneshige.kenji@jp.fujitsu.com>
Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>
drivers/pci/pcie/aspm.c

index 8f7884a8bc2e97fcd0f3fddd04c4c43f2075bbe0..79f6d61798fa900b3c006260fba8f0e1f6485bde 100644 (file)
@@ -42,6 +42,7 @@ struct pcie_link_state {
        /* ASPM state */
        u32 aspm_support:2;             /* Supported ASPM state */
        u32 aspm_enabled:2;             /* Enabled ASPM state */
+       u32 aspm_capable:2;             /* Capable ASPM state with latency */
        u32 aspm_default:2;             /* Default ASPM state by BIOS */
        u32 aspm_disable:2;             /* Disabled ASPM state */
 
@@ -316,6 +317,39 @@ static void pcie_aspm_get_cap_device(struct pci_dev *pdev, u32 *state,
        *enabled = reg16 & (PCIE_LINK_STATE_L0S | PCIE_LINK_STATE_L1);
 }
 
+static void pcie_aspm_check_latency(struct pci_dev *endpoint)
+{
+       u32 l1_switch_latency = 0;
+       struct aspm_latency *acceptable;
+       struct pcie_link_state *link;
+
+       /* Device not in D0 doesn't need latency check */
+       if ((endpoint->current_state != PCI_D0) &&
+           (endpoint->current_state != PCI_UNKNOWN))
+               return;
+
+       link = endpoint->bus->self->link_state;
+       acceptable = &link->acceptable[PCI_FUNC(endpoint->devfn)];
+
+       while (link) {
+               /* Check L0s latency */
+               if ((link->aspm_capable & PCIE_LINK_STATE_L0S) &&
+                   (link->latency.l0s > acceptable->l0s))
+                       link->aspm_capable &= ~PCIE_LINK_STATE_L0S;
+               /*
+                * Check L1 latency.
+                * Every switch on the path to root complex need 1
+                * more microsecond for L1. Spec doesn't mention L0s.
+                */
+               if ((link->aspm_capable & PCIE_LINK_STATE_L1) &&
+                   (link->latency.l1 + l1_switch_latency > acceptable->l1))
+                       link->aspm_capable &= ~PCIE_LINK_STATE_L1;
+               l1_switch_latency += 1000;
+
+               link = link->parent;
+       }
+}
+
 static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist)
 {
        u32 support, l0s, l1, enabled;
@@ -348,6 +382,9 @@ static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist)
 
        /* Save default state */
        link->aspm_default = link->aspm_enabled;
+
+       /* Setup initial capable state. Will be updated later */
+       link->aspm_capable = link->aspm_support;
        /*
         * If the downstream component has pci bridge function, don't
         * do ASPM for now.
@@ -376,12 +413,14 @@ static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist)
 
                pos = pci_find_capability(child, PCI_CAP_ID_EXP);
                pci_read_config_dword(child, pos + PCI_EXP_DEVCAP, &reg32);
+               /* Calculate endpoint L0s acceptable latency */
                encoding = (reg32 & PCI_EXP_DEVCAP_L0S) >> 6;
                acceptable->l0s = calc_l0s_acceptable(encoding);
-               if (link->aspm_support & PCIE_LINK_STATE_L1) {
-                       encoding = (reg32 & PCI_EXP_DEVCAP_L1) >> 9;
-                       acceptable->l1 = calc_l1_acceptable(encoding);
-               }
+               /* Calculate endpoint L1 acceptable latency */
+               encoding = (reg32 & PCI_EXP_DEVCAP_L1) >> 9;
+               acceptable->l1 = calc_l1_acceptable(encoding);
+
+               pcie_aspm_check_latency(child);
        }
 }
 
@@ -397,27 +436,10 @@ static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist)
  */
 static u32 __pcie_aspm_check_state_one(struct pci_dev *endpoint, u32 state)
 {
-       u32 l1_switch_latency = 0;
-       struct aspm_latency *acceptable;
-       struct pcie_link_state *link;
-
-       link = endpoint->bus->self->link_state;
-       state &= link->aspm_support;
-       acceptable = &link->acceptable[PCI_FUNC(endpoint->devfn)];
-
+       struct pcie_link_state *link = endpoint->bus->self->link_state;
        while (link && state) {
-               if ((state & PCIE_LINK_STATE_L0S) &&
-                   (link->latency.l0s > acceptable->l0s))
-                       state &= ~PCIE_LINK_STATE_L0S;
-               if ((state & PCIE_LINK_STATE_L1) &&
-                   (link->latency.l1 + l1_switch_latency > acceptable->l1))
-                       state &= ~PCIE_LINK_STATE_L1;
+               state &= link->aspm_capable;
                link = link->parent;
-               /*
-                * Every switch on the path to root complex need 1
-                * more microsecond for L1. Spec doesn't mention L0s.
-                */
-               l1_switch_latency += 1000;
        }
        return state;
 }
@@ -668,11 +690,35 @@ out:
        up_read(&pci_bus_sem);
 }
 
+/* Recheck latencies and update aspm_capable for links under the root */
+static void pcie_update_aspm_capable(struct pcie_link_state *root)
+{
+       struct pcie_link_state *link;
+       BUG_ON(root->parent);
+       list_for_each_entry(link, &link_list, sibling) {
+               if (link->root != root)
+                       continue;
+               link->aspm_capable = link->aspm_support;
+       }
+       list_for_each_entry(link, &link_list, sibling) {
+               struct pci_dev *child;
+               struct pci_bus *linkbus = link->pdev->subordinate;
+               if (link->root != root)
+                       continue;
+               list_for_each_entry(child, &linkbus->devices, bus_list) {
+                       if ((child->pcie_type != PCI_EXP_TYPE_ENDPOINT) &&
+                           (child->pcie_type != PCI_EXP_TYPE_LEG_END))
+                               continue;
+                       pcie_aspm_check_latency(child);
+               }
+       }
+}
+
 /* @pdev: the endpoint device */
 void pcie_aspm_exit_link_state(struct pci_dev *pdev)
 {
        struct pci_dev *parent = pdev->bus->self;
-       struct pcie_link_state *link;
+       struct pcie_link_state *link, *root;
 
        if (aspm_disabled || !pdev->is_pcie || !parent || !parent->link_state)
                return;
@@ -690,6 +736,7 @@ void pcie_aspm_exit_link_state(struct pci_dev *pdev)
                goto out;
 
        link = parent->link_state;
+       root = link->root;
 
        /* All functions are removed, so just disable ASPM for the link */
        __pcie_aspm_config_one_dev(parent, 0);
@@ -697,6 +744,9 @@ void pcie_aspm_exit_link_state(struct pci_dev *pdev)
        list_del(&link->link);
        /* Clock PM is for endpoint device */
        free_link_state(link);
+
+       /* Recheck latencies and configure upstream links */
+       pcie_update_aspm_capable(root);
 out:
        mutex_unlock(&aspm_lock);
        up_read(&pci_bus_sem);
@@ -705,18 +755,23 @@ out:
 /* @pdev: the root port or switch downstream port */
 void pcie_aspm_pm_state_change(struct pci_dev *pdev)
 {
-       struct pcie_link_state *link_state = pdev->link_state;
+       struct pcie_link_state *link = pdev->link_state;
 
-       if (aspm_disabled || !pdev->is_pcie || !pdev->link_state)
+       if (aspm_disabled || !pdev->is_pcie || !link)
                return;
-       if (pdev->pcie_type != PCI_EXP_TYPE_ROOT_PORT &&
-               pdev->pcie_type != PCI_EXP_TYPE_DOWNSTREAM)
+       if ((pdev->pcie_type != PCI_EXP_TYPE_ROOT_PORT) &&
+           (pdev->pcie_type != PCI_EXP_TYPE_DOWNSTREAM))
                return;
        /*
-        * devices changed PM state, we should recheck if latency meets all
-        * functions' requirement
+        * Devices changed PM state, we should recheck if latency
+        * meets all functions' requirement
         */
-       pcie_aspm_configure_link_state(link_state, link_state->aspm_enabled);
+       down_read(&pci_bus_sem);
+       mutex_lock(&aspm_lock);
+       pcie_update_aspm_capable(link->root);
+       __pcie_aspm_configure_link_state(link, link->aspm_enabled);
+       mutex_unlock(&aspm_lock);
+       up_read(&pci_bus_sem);
 }
 
 /*