Re: [patch] pm: fix runtime powermanagement's /sys interface

Patrick Mochel
2006-01-06T01:37:30Z

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: TEXT/PLAIN; charset=X-UNKNOWN, Size: 6120 bytes --]

On Fri, 6 Jan 2006, Pavel Machek wrote:

> On Čt 05-01-06 16:04:07, Patrick Mochel wrote:

> > A better point, and one that would actually be useful, would be to remove
> > the file altogether. Let Dominik export a power file, with complete
> > control over the values, for each pcmcia device. Then you never have to
> > worry about breaking PCMCIA again.
>
> Fine with me.

ACK, you beat me to it.

And, appended is a patch to export PM controls for PCI devices. The file
"pm_possible_states" exports the states a device supports, and "pm_state"
exports the current state (and provides the interface for entering a
state).

Eventually, some drivers will want to fix up those values so that it can
mask of states that it doesn't support, as well as offer possible device-
specific states.

What's interesting is that with this patch, I can see that two more
devices on my system support D1 and D2 -- the cardbus controllers, which
are actually bridges whose PM capabilities aren't exported via lspci.

Thanks,

Patrick

diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index 6707df9..628f3a3 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -36,6 +36,10 @@ obj-$(CONFIG_ACPI) += pci-acpi.o
# Cardbus & CompactPCI use setup-bus
obj-$(CONFIG_HOTPLUG) += setup-bus.o

+
+# Power Management functionality
+obj-$(CONFIG_PM) += pm.o
+
ifndef CONFIG_X86
obj-y += syscall.o
endif
diff --git a/drivers/pci/bus.c b/drivers/pci/bus.c
index eed67d9..83045d9 100644
--- a/drivers/pci/bus.c
+++ b/drivers/pci/bus.c
@@ -85,6 +85,8 @@ void __devinit pci_bus_add_device(struct
list_add_tail(&dev->global_list, &pci_devices);
spin_unlock(&pci_bus_lock);

+ pci_pm_init(dev);
+
pci_proc_attach_device(dev);
pci_create_sysfs_dev_files(dev);
}
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 6527b36..6d7afbc 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -63,6 +63,23 @@ extern int pcie_mch_quirk;
extern struct device_attribute pci_dev_attrs[];
extern struct class_device_attribute class_device_attr_cpuaffinity;

+
+#ifdef CONFIG_PM
+extern int pci_pm_init(struct pci_dev *);
+extern void pci_pm_exit(struct pci_dev *);
+#else /* CONFIG_PM */
+static inline int pci_pm_init(struct pci_dev *)
+{
+ return 0;
+}
+
+static inline void pci_pm_exit(struct pci_dev *)
+{
+
+}
+
+#endif
+
/**
* pci_match_one_device - Tell if a PCI device structure has a matching
* PCI device id structure
diff --git a/drivers/pci/pm.c b/drivers/pci/pm.c
new file mode 100644
index 0000000..ce476e4
--- /dev/null
+++ b/drivers/pci/pm.c
@@ -0,0 +1,138 @@
+/*
+ * drivers/pci/pm.c - Power management support for PCI devices
+ */
+
+#include <linux/pci.h>
+
+
+static ssize_t pm_possible_states_show(struct device * d,
+ struct device_attribute * a,
+ char * buffer)
+{
+ struct pci_dev * dev = to_pci_dev(d);
+ char * s = buffer;
+
+ s += sprintf(s, "d0");
+ if (dev->poss_states[PCI_D1])
+ s += sprintf(s, " d1");
+ if (dev->poss_states[PCI_D2])
+ s += sprintf(s, " d2");
+ if (dev->poss_states[PCI_D3hot])
+ s += sprintf(s, " d3");
+ s += sprintf(s, "\n");
+ return (s - buffer);
+}
+
+static DEVICE_ATTR(pm_possible_states, 0444, pm_possible_states_show, NULL);
+
+
+static ssize_t pm_state_show(struct device * d, struct device_attribute * a,
+ char * buffer)
+{
+ struct pci_dev * dev = to_pci_dev(d);
+ const char * str;
+
+ switch (dev->current_state) {
+ case PCI_D0:
+ str = "d0";
+ break;
+ case PCI_D1:
+ str = "d1";
+ break;
+ case PCI_D2:
+ str = "d2";
+ break;
+ case PCI_D3hot:
+ str = "d3";
+ break;
+ default:
+ str = "d?";
+ break;
+ }
+
+ return sprintf(buffer, "%s\n", str);
+}
+
+
+static ssize_t pm_state_store(struct device * d, struct device_attribute * a,
+ const char * buffer, size_t len)
+{
+ struct pci_dev * dev = to_pci_dev(d);
+ pci_power_t state;
+ int ret;
+
+ if (!strnicmp(buffer, "d0", len))
+ state = PCI_D0;
+ else if (!strnicmp(buffer, "d1", len))
+ state = PCI_D1;
+ else if (!strnicmp(buffer, "d2", len))
+ state = PCI_D2;
+ else if (!strnicmp(buffer, "d3", len))
+ state = PCI_D3hot;
+ else
+ return -EINVAL;
+
+ if (state == dev->current_state)
+ return 0;
+
+ if (dev->poss_states[state])
+ ret = pci_set_power_state(dev, state);
+ else
+ ret = -EINVAL;
+
+ return ret == 0 ? len : ret;
+}
+
+static DEVICE_ATTR(pm_state, 0644, pm_state_show, pm_state_store);
+
+
+static int find_states(struct pci_dev * dev)
+{
+ int cap;
+ u16 pmc;
+
+
+ /*
+ * Every device supports D0
+ */
+ dev->poss_states[PCI_D0] = 1;
+
+ /*
+ * Check if the device has PM capabilties in the config space
+ */
+ cap = pci_find_capability(dev, PCI_CAP_ID_PM);
+ if (!cap)
+ return -EIO;
+
+ /*
+ * If it supports PM capabilities, it will support D3
+ */
+ dev->poss_states[PCI_D3hot] = 1;
+
+ /*
+ * Check D1 and D2 support
+ */
+ pci_read_config_word(dev, cap + PCI_PM_PMC, &pmc);
+ if (pmc & PCI_PM_CAP_D1)
+ dev->poss_states[PCI_D1] = 1;
+ if (pmc & PCI_PM_CAP_D2)
+ dev->poss_states[PCI_D2] = 1;
+ return 0;
+}
+
+
+int pci_pm_init(struct pci_dev * dev)
+{
+ if (find_states(dev))
+ return 0;
+
+ device_create_file(&dev->dev, &dev_attr_pm_possible_states);
+ return device_create_file(&dev->dev, &dev_attr_pm_state);
+}
+
+void pci_pm_exit(struct pci_dev * dev)
+{
+ device_remove_file(&dev->dev, &dev_attr_pm_state);
+ device_remove_file(&dev->dev, &dev_attr_pm_possible_states);
+}
+
diff --git a/include/linux/pci.h b/include/linux/pci.h
index de690ca..2600119 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -106,6 +106,7 @@ struct pci_dev {
this if your device has broken DMA
or supports 64-bit transfers. */

+ u32 poss_states[4];
pci_power_t current_state; /* Current operating state. In ACPI-speak,
this is D0-D3, D0 being fully functional,
and D3 being off. */

[-- Attachment #2: Type: text/plain, Size: 0 bytes --]