Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

PCI: Rework ASPM disable code

Right now we forcibly clear ASPM state on all devices if the BIOS indicates
that the feature isn't supported. Based on the Microsoft presentation
"PCI Express In Depth for Windows Vista and Beyond", I'm starting to think
that this may be an error. The implication is that unless the platform
grants full control via _OSC, Windows will not touch any PCIe features -
including ASPM. In that case clearing ASPM state would be an error unless
the platform has granted us that control.

This patch reworks the ASPM disabling code such that the actual clearing
of state is triggered by a successful handoff of PCIe control to the OS.
The general ASPM code undergoes some changes in order to ensure that the
ability to clear the bits isn't overridden by ASPM having already been
disabled. Further, this theoretically now allows for situations where
only a subset of PCIe roots hand over control, leaving the others in the
BIOS state.

It's difficult to know for sure that this is the right thing to do -
there's zero public documentation on the interaction between all of these
components. But enough vendors enable ASPM on platforms and then set this
bit that it seems likely that they're expecting the OS to leave them alone.

Measured to save around 5W on an idle Thinkpad X220.

Signed-off-by: Matthew Garrett <mjg@redhat.com>
Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>

authored by

Matthew Garrett and committed by
Jesse Barnes
10f6dc7e cfa4d8cc

+46 -24
+7
drivers/acpi/pci_root.c
··· 596 596 if (ACPI_SUCCESS(status)) { 597 597 dev_info(root->bus->bridge, 598 598 "ACPI _OSC control (0x%02x) granted\n", flags); 599 + if (acpi_gbl_FADT.boot_flags & ACPI_FADT_NO_ASPM) { 600 + /* 601 + * We have ASPM control, but the FADT indicates 602 + * that it's unsupported. Clear it. 603 + */ 604 + pcie_clear_aspm(root->bus); 605 + } 599 606 } else { 600 607 dev_info(root->bus->bridge, 601 608 "ACPI _OSC request failed (%s), "
-1
drivers/pci/pci-acpi.c
··· 395 395 396 396 if (acpi_gbl_FADT.boot_flags & ACPI_FADT_NO_ASPM) { 397 397 printk(KERN_INFO"ACPI FADT declares the system doesn't support PCIe ASPM, so disable it\n"); 398 - pcie_clear_aspm(); 399 398 pcie_no_aspm(); 400 399 } 401 400
+37 -21
drivers/pci/pcie/aspm.c
··· 68 68 struct aspm_latency acceptable[8]; 69 69 }; 70 70 71 - static int aspm_disabled, aspm_force, aspm_clear_state; 71 + static int aspm_disabled, aspm_force; 72 72 static bool aspm_support_enabled = true; 73 73 static DEFINE_MUTEX(aspm_lock); 74 74 static LIST_HEAD(link_list); ··· 500 500 int pos; 501 501 u32 reg32; 502 502 503 - if (aspm_clear_state) 504 - return -EINVAL; 505 - 506 503 /* 507 504 * Some functions in a slot might not all be PCIe functions, 508 505 * very strange. Disable ASPM for the whole slot ··· 571 574 pdev->pcie_type != PCI_EXP_TYPE_DOWNSTREAM) 572 575 return; 573 576 574 - if (aspm_disabled && !aspm_clear_state) 575 - return; 576 - 577 577 /* VIA has a strange chipset, root port is under a bridge */ 578 578 if (pdev->pcie_type == PCI_EXP_TYPE_ROOT_PORT && 579 579 pdev->bus->self) ··· 602 608 * the BIOS's expectation, we'll do so once pci_enable_device() is 603 609 * called. 604 610 */ 605 - if (aspm_policy != POLICY_POWERSAVE || aspm_clear_state) { 611 + if (aspm_policy != POLICY_POWERSAVE) { 606 612 pcie_config_aspm_path(link); 607 613 pcie_set_clkpm(link, policy_to_clkpm_state(link)); 608 614 } ··· 643 649 struct pci_dev *parent = pdev->bus->self; 644 650 struct pcie_link_state *link, *root, *parent_link; 645 651 646 - if ((aspm_disabled && !aspm_clear_state) || !pci_is_pcie(pdev) || 647 - !parent || !parent->link_state) 652 + if (!pci_is_pcie(pdev) || !parent || !parent->link_state) 648 653 return; 649 654 if ((parent->pcie_type != PCI_EXP_TYPE_ROOT_PORT) && 650 655 (parent->pcie_type != PCI_EXP_TYPE_DOWNSTREAM)) ··· 727 734 * pci_disable_link_state - disable pci device's link state, so the link will 728 735 * never enter specific states 729 736 */ 730 - static void __pci_disable_link_state(struct pci_dev *pdev, int state, bool sem) 737 + static void __pci_disable_link_state(struct pci_dev *pdev, int state, bool sem, 738 + bool force) 731 739 { 732 740 struct pci_dev *parent = pdev->bus->self; 733 741 struct pcie_link_state *link; 734 742 735 - if (aspm_disabled || !pci_is_pcie(pdev)) 743 + if (aspm_disabled && !force) 736 744 return; 745 + 746 + if (!pci_is_pcie(pdev)) 747 + return; 748 + 737 749 if (pdev->pcie_type == PCI_EXP_TYPE_ROOT_PORT || 738 750 pdev->pcie_type == PCI_EXP_TYPE_DOWNSTREAM) 739 751 parent = pdev; ··· 766 768 767 769 void pci_disable_link_state_locked(struct pci_dev *pdev, int state) 768 770 { 769 - __pci_disable_link_state(pdev, state, false); 771 + __pci_disable_link_state(pdev, state, false, false); 770 772 } 771 773 EXPORT_SYMBOL(pci_disable_link_state_locked); 772 774 773 775 void pci_disable_link_state(struct pci_dev *pdev, int state) 774 776 { 775 - __pci_disable_link_state(pdev, state, true); 777 + __pci_disable_link_state(pdev, state, true, false); 776 778 } 777 779 EXPORT_SYMBOL(pci_disable_link_state); 780 + 781 + void pcie_clear_aspm(struct pci_bus *bus) 782 + { 783 + struct pci_dev *child; 784 + 785 + /* 786 + * Clear any ASPM setup that the firmware has carried out on this bus 787 + */ 788 + list_for_each_entry(child, &bus->devices, bus_list) { 789 + __pci_disable_link_state(child, PCIE_LINK_STATE_L0S | 790 + PCIE_LINK_STATE_L1 | 791 + PCIE_LINK_STATE_CLKPM, 792 + false, true); 793 + } 794 + } 778 795 779 796 static int pcie_aspm_set_policy(const char *val, struct kernel_param *kp) 780 797 { ··· 948 935 static int __init pcie_aspm_disable(char *str) 949 936 { 950 937 if (!strcmp(str, "off")) { 938 + aspm_policy = POLICY_DEFAULT; 951 939 aspm_disabled = 1; 952 940 aspm_support_enabled = false; 953 941 printk(KERN_INFO "PCIe ASPM is disabled\n"); ··· 961 947 962 948 __setup("pcie_aspm=", pcie_aspm_disable); 963 949 964 - void pcie_clear_aspm(void) 965 - { 966 - if (!aspm_force) 967 - aspm_clear_state = 1; 968 - } 969 - 970 950 void pcie_no_aspm(void) 971 951 { 972 - if (!aspm_force) 952 + /* 953 + * Disabling ASPM is intended to prevent the kernel from modifying 954 + * existing hardware state, not to clear existing state. To that end: 955 + * (a) set policy to POLICY_DEFAULT in order to avoid changing state 956 + * (b) prevent userspace from changing policy 957 + */ 958 + if (!aspm_force) { 959 + aspm_policy = POLICY_DEFAULT; 973 960 aspm_disabled = 1; 961 + } 974 962 } 975 963 976 964 /**
+2 -2
include/linux/pci-aspm.h
··· 29 29 extern void pcie_aspm_powersave_config_link(struct pci_dev *pdev); 30 30 extern void pci_disable_link_state(struct pci_dev *pdev, int state); 31 31 extern void pci_disable_link_state_locked(struct pci_dev *pdev, int state); 32 - extern void pcie_clear_aspm(void); 32 + extern void pcie_clear_aspm(struct pci_bus *bus); 33 33 extern void pcie_no_aspm(void); 34 34 #else 35 35 static inline void pcie_aspm_init_link_state(struct pci_dev *pdev) ··· 47 47 static inline void pci_disable_link_state(struct pci_dev *pdev, int state) 48 48 { 49 49 } 50 - static inline void pcie_clear_aspm(void) 50 + static inline void pcie_clear_aspm(struct pci_bus *bus) 51 51 { 52 52 } 53 53 static inline void pcie_no_aspm(void)