PCI: Speed up algorithm in pci_bridge_d3_update()
authorLukas Wunner <lukas@wunner.de>
Fri, 28 Oct 2016 08:52:06 +0000 (10:52 +0200)
committerBjorn Helgaas <bhelgaas@google.com>
Fri, 18 Nov 2016 00:45:35 +0000 (18:45 -0600)
After a device has been added, removed or had its D3cold attributes
changed, we recheck whether its parent bridge may runtime suspend to D3hot
with pci_bridge_d3_update().

The most naive algorithm would be to iterate over the bridge's children and
check if any of them are blocking D3.

The function already tries to be a bit smarter than that by first checking
the device that was changed.  If this device already blocks D3 on the
bridge, then walking over all the other children can be skipped.  A
drawback of this approach is that if the device is *not* blocking D3, it
will be checked a second time by pci_walk_bus().  But that's cheap and is
outweighed by the performance gain of potentially skipping pci_walk_bus()
altogether.

The algorithm can be optimized further by taking into account if D3 is
currently allowed for the bridge, as shown in the following truth table:

(a)  remove &&  bridge_d3:  D3 is currently allowed for the bridge and
                            removing one of its children won't change
                            that.  No action necessary.
(b)  remove && !bridge_d3:  D3 may now be allowed for the bridge if the
                            removed child was the only one blocking it.
                            Check all its siblings to verify that.
(c) !remove &&  bridge_d3:  D3 may now be disallowed but this can only
                            be caused by the added/changed child, not
                            any of its siblings.  Check only that single
                            device.
(d) !remove && !bridge_d3:  D3 may now be allowed for the bridge if the
                            changed child was the only one blocking it.
                            Check all its siblings to verify that.
                            By checking beforehand if the changed child
                            is blocking D3, we may be able to skip
                            checking its siblings.

Currently we do not special-case option (a) and in case of option (c) we
gratuitously call pci_walk_bus().  Speed up the algorithm by adding these
optimizations.  Reword the comments a bit in an attempt to improve clarity.

No functional change intended.

Tested-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Signed-off-by: Lukas Wunner <lukas@wunner.de>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Reviewed-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
drivers/pci/pci.c

index 8200874ef5fc534c699c73a9b8afd300541da14c..4a7f6f54d669e52c15b82e30c4166f616d6313d1 100644 (file)
@@ -2297,20 +2297,32 @@ void pci_bridge_d3_update(struct pci_dev *dev)
                return;
 
        /*
-        * If the device is removed we do not care about its D3cold
-        * capabilities.
+        * If D3 is currently allowed for the bridge, removing one of its
+        * children won't change that.
+        */
+       if (remove && bridge->bridge_d3)
+               return;
+
+       /*
+        * If D3 is currently allowed for the bridge and a child is added or
+        * changed, disallowance of D3 can only be caused by that child, so
+        * we only need to check that single device, not any of its siblings.
+        *
+        * If D3 is currently not allowed for the bridge, checking the device
+        * first may allow us to skip checking its siblings.
         */
        if (!remove)
                pci_dev_check_d3cold(dev, &d3cold_ok);
 
-       if (d3cold_ok) {
-               /*
-                * We need to go through all children to find out if all of
-                * them can still go to D3cold.
-                */
+       /*
+        * If D3 is currently not allowed for the bridge, this may be caused
+        * either by the device being changed/removed or any of its siblings,
+        * so we need to go through all children to find out if one of them
+        * continues to block D3.
+        */
+       if (d3cold_ok && !bridge->bridge_d3)
                pci_walk_bus(bridge->subordinate, pci_dev_check_d3cold,
                             &d3cold_ok);
-       }
 
        if (bridge->bridge_d3 != d3cold_ok) {
                bridge->bridge_d3 = d3cold_ok;