@@ -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/.../power/aspm_link_states
+Date: May 2019
+Contact: Heiner Kallweit <hkallweit1@gmail.com>
+Description:
+ If ASPM is supported for an endpoint, then this file can be
+ used to enable / disable link states. A link state
+ displayed in brackets is enabled, otherwise it's disabled.
+ To control link states (case insensitive):
+ +state : enables a supported state
+ -state : disables a state
+ none : disables all link states
+ all : enables all supported link states
@@ -498,17 +498,13 @@ void pcie_aspm_init_link_state(struct pci_dev *pdev);
void pcie_aspm_exit_link_state(struct pci_dev *pdev);
void pcie_aspm_pm_state_change(struct pci_dev *pdev);
void pcie_aspm_powersave_config_link(struct pci_dev *pdev);
+void pcie_aspm_create_sysfs_dev_files(struct pci_dev *pdev);
+void pcie_aspm_remove_sysfs_dev_files(struct pci_dev *pdev);
#else
static inline void pcie_aspm_init_link_state(struct pci_dev *pdev) { }
static inline void pcie_aspm_exit_link_state(struct pci_dev *pdev) { }
static inline void pcie_aspm_pm_state_change(struct pci_dev *pdev) { }
static inline void pcie_aspm_powersave_config_link(struct pci_dev *pdev) { }
-#endif
-
-#ifdef CONFIG_PCIEASPM_DEBUG
-void pcie_aspm_create_sysfs_dev_files(struct pci_dev *pdev);
-void pcie_aspm_remove_sysfs_dev_files(struct pci_dev *pdev);
-#else
static inline void pcie_aspm_create_sysfs_dev_files(struct pci_dev *pdev) { }
static inline void pcie_aspm_remove_sysfs_dev_files(struct pci_dev *pdev) { }
#endif
@@ -42,6 +42,8 @@
#define ASPM_STATE_ALL (ASPM_STATE_L0S | ASPM_STATE_L1 | \
ASPM_STATE_L1SS)
+static const char power_group[] = "power";
+
struct aspm_latency {
u32 l0s; /* L0s latency (nsec) */
u32 l1; /* L1 latency (nsec) */
@@ -1236,38 +1238,212 @@ static ssize_t clk_ctl_store(struct device *dev,
static DEVICE_ATTR_RW(link_state);
static DEVICE_ATTR_RW(clk_ctl);
+#endif
+
+struct aspm_sysfs_state {
+ const char *name;
+ int mask;
+};
+
+static const struct aspm_sysfs_state aspm_sysfs_states[] = {
+ { "L0S", ASPM_STATE_L0S },
+ { "L1", ASPM_STATE_L1 },
+ { "L1.1", ASPM_STATE_L1_1_MASK },
+ { "L1.2", ASPM_STATE_L1_2_MASK },
+};
+
+static struct pcie_link_state *aspm_get_parent_link(struct pci_dev *pdev)
+{
+ struct pci_dev *parent = pdev->bus->self;
+
+ if (pdev->has_secondary_link)
+ parent = pdev;
+
+ return parent ? parent->link_state : NULL;
+}
+
+static bool pcie_check_aspm_endpoint(struct pci_dev *pdev)
+{
+ struct pcie_link_state *link;
+
+ if (pci_pcie_type(pdev) != PCI_EXP_TYPE_ENDPOINT)
+ return false;
+
+ link = aspm_get_parent_link(pdev);
+
+ return link && link->aspm_support;
+}
+
+static ssize_t aspm_link_states_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct pcie_link_state *link;
+ int len = 0, i;
+
+ link = aspm_get_parent_link(pdev);
+ if (!link)
+ return -EOPNOTSUPP;
+
+ mutex_lock(&aspm_lock);
+
+ for (i = 0; i < ARRAY_SIZE(aspm_sysfs_states); i++) {
+ const struct aspm_sysfs_state *st = aspm_sysfs_states + i;
+
+ if (link->aspm_enabled & st->mask)
+ len += scnprintf(buf + len, PAGE_SIZE - len, "[%s] ",
+ st->name);
+ else
+ len += scnprintf(buf + len, PAGE_SIZE - len, "%s ",
+ st->name);
+ }
+
+ if (link->clkpm_enabled)
+ len += scnprintf(buf + len, PAGE_SIZE - len, "[CLKPM] ");
+ else
+ len += scnprintf(buf + len, PAGE_SIZE - len, "CLKPM ");
+
+ mutex_unlock(&aspm_lock);
+
+ len += scnprintf(buf + len, PAGE_SIZE - len, "\n");
+
+ return len;
+}
+
+static ssize_t aspm_link_states_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;
+ char *buftmp = (char *)buf, *tok;
+ unsigned int disable_aspm, disable_clkpm;
+ bool first = true, add;
+ int err = 0, i;
+
+ if (aspm_disabled)
+ return -EPERM;
+
+ link = aspm_get_parent_link(pdev);
+ if (!link)
+ return -EOPNOTSUPP;
+
+ down_read(&pci_bus_sem);
+ mutex_lock(&aspm_lock);
+
+ disable_aspm = link->aspm_disable;
+ disable_clkpm = link->clkpm_disable;
+
+ while ((tok = strsep(&buftmp, " \n")) != NULL) {
+ bool found = false;
+
+ if (!*tok)
+ continue;
+
+ if (first) {
+ if (!strcasecmp(tok, "none")) {
+ disable_aspm = ASPM_STATE_ALL;
+ disable_clkpm = 1;
+ break;
+ }
+ if (!strcasecmp(tok, "all")) {
+ disable_aspm = 0;
+ disable_clkpm = 0;
+ break;
+ }
+ first = false;
+ }
+
+ if (*tok != '+' && *tok != '-') {
+ err = -EINVAL;
+ goto out;
+ }
+
+ add = *tok++ == '+';
+
+ for (i = 0; i < ARRAY_SIZE(aspm_sysfs_states); i++) {
+ const struct aspm_sysfs_state *st =
+ aspm_sysfs_states + i;
+
+ if (!strcasecmp(tok, st->name)) {
+ if (add)
+ disable_aspm &= ~st->mask;
+ else
+ disable_aspm |= st->mask;
+ found = true;
+ break;
+ }
+ }
+
+ if (!found && !strcasecmp(tok, "clkpm")) {
+ disable_clkpm = add ? 0 : 1;
+ found = true;
+ }
+
+ if (!found) {
+ err = -EINVAL;
+ goto out;
+ }
+ }
+
+ if (disable_aspm & ASPM_STATE_L1)
+ disable_aspm |= ASPM_STATE_L1SS;
+
+ link->aspm_disable = disable_aspm;
+ link->clkpm_disable = disable_clkpm;
+
+ pcie_config_aspm_link(link, policy_to_aspm_state(link));
+ pcie_set_clkpm(link, policy_to_clkpm_state(link));
+out:
+ mutex_unlock(&aspm_lock);
+ up_read(&pci_bus_sem);
+
+ return err ?: len;
+}
+
+static DEVICE_ATTR_RW(aspm_link_states);
-static char power_group[] = "power";
void pcie_aspm_create_sysfs_dev_files(struct pci_dev *pdev)
{
struct pcie_link_state *link_state = pdev->link_state;
+ if (pcie_check_aspm_endpoint(pdev))
+ sysfs_add_file_to_group(&pdev->dev.kobj,
+ &dev_attr_aspm_link_states.attr, power_group);
+
if (!link_state)
return;
+#ifdef CONFIG_PCIEASPM_DEBUG
if (link_state->aspm_support)
sysfs_add_file_to_group(&pdev->dev.kobj,
&dev_attr_link_state.attr, power_group);
if (link_state->clkpm_capable)
sysfs_add_file_to_group(&pdev->dev.kobj,
&dev_attr_clk_ctl.attr, power_group);
+#endif
}
void pcie_aspm_remove_sysfs_dev_files(struct pci_dev *pdev)
{
struct pcie_link_state *link_state = pdev->link_state;
+ if (pcie_check_aspm_endpoint(pdev))
+ sysfs_remove_file_from_group(&pdev->dev.kobj,
+ &dev_attr_aspm_link_states.attr, power_group);
+
if (!link_state)
return;
+#ifdef CONFIG_PCIEASPM_DEBUG
if (link_state->aspm_support)
sysfs_remove_file_from_group(&pdev->dev.kobj,
&dev_attr_link_state.attr, power_group);
if (link_state->clkpm_capable)
sysfs_remove_file_from_group(&pdev->dev.kobj,
&dev_attr_clk_ctl.attr, power_group);
-}
#endif
+}
static int __init pcie_aspm_disable(char *str)
{