PCI/ASPM: Add sysfs attributes for controlling ASPM link states

Add sysfs attributes to Endpoints and other Upstream Ports to control ASPM,
Clock PM, and L1 PM Substates.  The new attributes are:

  /sys/devices/pci*/.../link/clkpm
  /sys/devices/pci*/.../link/l0s_aspm
  /sys/devices/pci*/.../link/l1_aspm
  /sys/devices/pci*/.../link/l1_1_aspm
  /sys/devices/pci*/.../link/l1_2_aspm
  /sys/devices/pci*/.../link/l1_1_pcipm
  /sys/devices/pci*/.../link/l1_2_pcipm

An attribute is only visible if both ends of the Link leading to the device
support the state.  Writing y/1/on to the file enables the state; n/0/off
disables it.

These attributes can be used to tune the power/performance tradeoff for
individual devices.

[bhelgaas: commit log, rename directory to "link"]
Link: https://lore.kernel.org/r/b1c83f8a-9bf6-eac5-82d0-cf5b90128fbf@gmail.com
Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
This commit is contained in:
Heiner Kallweit 2019-10-05 14:07:56 +02:00 committed by Bjorn Helgaas
parent 687aaf386a
commit 72ea91afbf
4 changed files with 169 additions and 0 deletions

View File

@ -347,3 +347,16 @@ Description:
If the device has any Peer-to-Peer memory registered, this
file contains a '1' if the memory has been published for
use outside the driver that owns the device.
What: /sys/bus/pci/devices/.../link/clkpm
/sys/bus/pci/devices/.../link/l0s_aspm
/sys/bus/pci/devices/.../link/l1_aspm
/sys/bus/pci/devices/.../link/l1_1_aspm
/sys/bus/pci/devices/.../link/l1_2_aspm
/sys/bus/pci/devices/.../link/l1_1_pcipm
/sys/bus/pci/devices/.../link/l1_2_pcipm
Date: October 2019
Contact: Heiner Kallweit <hkallweit1@gmail.com>
Description: If ASPM is supported for an endpoint, these files can be
used to disable or enable the individual power management
states. Write y/1/on to enable, n/0/off to disable.

View File

@ -1587,6 +1587,9 @@ static const struct attribute_group *pci_dev_attr_groups[] = {
&pcie_dev_attr_group,
#ifdef CONFIG_PCIEAER
&aer_stats_attr_group,
#endif
#ifdef CONFIG_PCIEASPM
&aspm_ctrl_attr_group,
#endif
NULL,
};

View File

@ -667,4 +667,8 @@ static inline int pci_acpi_program_hp_params(struct pci_dev *dev)
}
#endif
#ifdef CONFIG_PCIEASPM
extern const struct attribute_group aspm_ctrl_attr_group;
#endif
#endif /* DRIVERS_PCI_H */

View File

@ -899,6 +899,14 @@ static struct pcie_link_state *alloc_pcie_link_state(struct pci_dev *pdev)
return link;
}
static void pcie_aspm_update_sysfs_visibility(struct pci_dev *pdev)
{
struct pci_dev *child;
list_for_each_entry(child, &pdev->subordinate->devices, bus_list)
sysfs_update_group(&child->dev.kobj, &aspm_ctrl_attr_group);
}
/*
* pcie_aspm_init_link_state: Initiate PCI express link state.
* It is called after the pcie and its children devices are scanned.
@ -960,6 +968,8 @@ void pcie_aspm_init_link_state(struct pci_dev *pdev)
pcie_set_clkpm(link, policy_to_clkpm_state(link));
}
pcie_aspm_update_sysfs_visibility(pdev);
unlock:
mutex_unlock(&aspm_lock);
out:
@ -1313,6 +1323,145 @@ void pcie_aspm_remove_sysfs_dev_files(struct pci_dev *pdev)
}
#endif
static ssize_t aspm_attr_show_common(struct device *dev,
struct device_attribute *attr,
char *buf, u8 state)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct pcie_link_state *link = pcie_aspm_get_link(pdev);
return sprintf(buf, "%d\n", (link->aspm_enabled & state) ? 1 : 0);
}
static ssize_t aspm_attr_store_common(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len, u8 state)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct pcie_link_state *link = pcie_aspm_get_link(pdev);
bool state_enable;
if (strtobool(buf, &state_enable) < 0)
return -EINVAL;
down_read(&pci_bus_sem);
mutex_lock(&aspm_lock);
if (state_enable) {
link->aspm_disable &= ~state;
/* need to enable L1 for substates */
if (state & ASPM_STATE_L1SS)
link->aspm_disable &= ~ASPM_STATE_L1;
} else {
link->aspm_disable |= state;
}
pcie_config_aspm_link(link, policy_to_aspm_state(link));
mutex_unlock(&aspm_lock);
up_read(&pci_bus_sem);
return len;
}
#define ASPM_ATTR(_f, _s) \
static ssize_t _f##_show(struct device *dev, \
struct device_attribute *attr, char *buf) \
{ return aspm_attr_show_common(dev, attr, buf, ASPM_STATE_##_s); } \
\
static ssize_t _f##_store(struct device *dev, \
struct device_attribute *attr, \
const char *buf, size_t len) \
{ return aspm_attr_store_common(dev, attr, buf, len, ASPM_STATE_##_s); }
ASPM_ATTR(l0s_aspm, L0S)
ASPM_ATTR(l1_aspm, L1)
ASPM_ATTR(l1_1_aspm, L1_1)
ASPM_ATTR(l1_2_aspm, L1_2)
ASPM_ATTR(l1_1_pcipm, L1_1_PCIPM)
ASPM_ATTR(l1_2_pcipm, L1_2_PCIPM)
static ssize_t clkpm_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct pcie_link_state *link = pcie_aspm_get_link(pdev);
return sprintf(buf, "%d\n", link->clkpm_enabled);
}
static ssize_t clkpm_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct pcie_link_state *link = pcie_aspm_get_link(pdev);
bool state_enable;
if (strtobool(buf, &state_enable) < 0)
return -EINVAL;
down_read(&pci_bus_sem);
mutex_lock(&aspm_lock);
link->clkpm_disable = !state_enable;
pcie_set_clkpm(link, policy_to_clkpm_state(link));
mutex_unlock(&aspm_lock);
up_read(&pci_bus_sem);
return len;
}
static DEVICE_ATTR_RW(clkpm);
static DEVICE_ATTR_RW(l0s_aspm);
static DEVICE_ATTR_RW(l1_aspm);
static DEVICE_ATTR_RW(l1_1_aspm);
static DEVICE_ATTR_RW(l1_2_aspm);
static DEVICE_ATTR_RW(l1_1_pcipm);
static DEVICE_ATTR_RW(l1_2_pcipm);
static struct attribute *aspm_ctrl_attrs[] = {
&dev_attr_clkpm.attr,
&dev_attr_l0s_aspm.attr,
&dev_attr_l1_aspm.attr,
&dev_attr_l1_1_aspm.attr,
&dev_attr_l1_2_aspm.attr,
&dev_attr_l1_1_pcipm.attr,
&dev_attr_l1_2_pcipm.attr,
NULL
};
static umode_t aspm_ctrl_attrs_are_visible(struct kobject *kobj,
struct attribute *a, int n)
{
struct device *dev = kobj_to_dev(kobj);
struct pci_dev *pdev = to_pci_dev(dev);
struct pcie_link_state *link = pcie_aspm_get_link(pdev);
static const u8 aspm_state_map[] = {
ASPM_STATE_L0S,
ASPM_STATE_L1,
ASPM_STATE_L1_1,
ASPM_STATE_L1_2,
ASPM_STATE_L1_1_PCIPM,
ASPM_STATE_L1_2_PCIPM,
};
if (aspm_disabled || !link)
return 0;
if (n == 0)
return link->clkpm_capable ? a->mode : 0;
return link->aspm_capable & aspm_state_map[n - 1] ? a->mode : 0;
}
const struct attribute_group aspm_ctrl_attr_group = {
.name = "link",
.attrs = aspm_ctrl_attrs,
.is_visible = aspm_ctrl_attrs_are_visible,
};
static int __init pcie_aspm_disable(char *str)
{
if (!strcmp(str, "off")) {