This patch updates the PCI subsystem to probe the PCI capability
registers in one pass. This data is stored in "struct pci_dev_pm". As
a result, we can avoid having to redetect PCI PM support in every PM
related call. Also, we don't have to access PCI config space as often.
"struct pci_dev_pm" will likely be expanded to include PM accounting
data in the near future.
--- a/drivers/pci/pm.c 2005-11-07 08:08:00.000000000 -0500
+++ b/drivers/pci/pm.c 2005-11-07 08:09:11.000000000 -0500
@@ -294,3 +294,60 @@
}
EXPORT_SYMBOL(pci_enable_wake);
+
+
+/*
+ * PCI PM capability detection and setup
+ */
+
+int pci_setup_device_pm(struct pci_dev *dev)
+{
+ int pm;
+ u16 pmcsr, pmc;
+ struct pci_dev_pm *pm_data;
+
+ pm = pci_find_capability(dev, PCI_CAP_ID_PM);
+ if (!pm) {
+ dev->current_state = PCI_D0;
+ return 0;
+ }
+
+ pci_read_config_word(dev,pm + PCI_PM_PMC,&pmc);
+
+ if ((pmc & PCI_PM_CAP_VER_MASK) > 3) {
+ printk(KERN_DEBUG
+ "PCI: %s has unsupported PM cap regs version (%u)\n",
+ pci_name(dev), pmc & PCI_PM_CAP_VER_MASK);
+ return -EIO;
+ }
+
+ dev->pm = pm_data = kmalloc(sizeof(struct pci_dev_pm), GFP_KERNEL);
+ if (!pm_data)
+ return -ENOMEM;
+
+ memset(pm_data, 0, sizeof(struct pci_dev_pm));
+
+ pm_data->pm_offset = pm;
+
+ /* determine supported device states */
+ /* all PM capable devices support at least D0 and D3 */
+ pm_data->state_mask |= ((1 << PCI_D0) | (1 << PCI_D3hot));
+ if (pmc & PCI_PM_CAP_D1)
+ pm_data->state_mask |= (1 << PCI_D1);
+ if (pmc & PCI_PM_CAP_D2)
+ pm_data->state_mask |= (1 << PCI_D2);
+
+ /* PME capabilities */
+ pm_data->pme_mask = ((pmc & PCI_PM_CAP_PME_MASK)
+ >> (ffs(PCI_PM_CAP_PME_MASK) - 1));
+
+ pci_read_config_word(dev, pm + PCI_PM_CTRL, &pmcsr);
+
+ /* device context retention */
+ pm_data->dsi = (pmc & PCI_PM_CAP_DSI) ? 1 : 0;
+ pm_data->no_soft_reset = (pmcsr & PCI_PM_CTRL_NO_SOFT_RESET) ? 1 : 0;
+
+ dev->current_state = (pmcsr & PCI_PM_CTRL_STATE_MASK);
+
+ return 0;
+}
--- a/drivers/pci/probe.c 2005-11-07 08:08:00.000000000 -0500
+++ b/drivers/pci/probe.c 2005-11-07 08:05:10.000000000 -0500
@@ -601,8 +601,7 @@
pr_debug("PCI: Found %s [%04x/%04x] %06x %02x\n", pci_name(dev),
dev->vendor, dev->device, class, dev->hdr_type);
- /* "Unknown power state" */
- dev->current_state = PCI_UNKNOWN;
+ pci_setup_device_pm(dev);
/* Early fixups, before probing the BARs */
pci_fixup_device(pci_fixup_early, dev);
--- a/include/linux/pci.h 2005-11-07 08:08:00.000000000 -0500
+++ b/include/linux/pci.h 2005-11-07 08:02:55.000000000 -0500
@@ -68,6 +68,18 @@
#define DEVICE_COUNT_COMPATIBLE 4
#define DEVICE_COUNT_RESOURCE 12
+struct pci_dev_pm {
+ unsigned int pm_offset; /* the PCI PM capability offset */
+
+ unsigned int dsi:1; /* vendor-specific initialization needed
+ after a reset */
+ unsigned int no_soft_reset:1; /* PCI config context retained when
+ going from D3_hot to D0 */
+
+ unsigned char state_mask; /* a mask of supported power states */
+ unsigned char pme_mask; /* a mask of power states that allow #PME */
+};
+
typedef int __bitwise pci_power_t;
#define PCI_D0 ((pci_power_t __force) 0)
@@ -78,6 +90,12 @@
#define PCI_UNKNOWN ((pci_power_t __force) 5)
#define PCI_POWER_ERROR ((pci_power_t __force) -1)
+#define PCI_PME_D0 0x01;
+#define PCI_PME_D1 0x02;
+#define PCI_PME_D2 0x04;
+#define PCI_PME_D3hot 0x08;
+#define PCI_PME_D3cold 0x10;
+
/*
* The pci_dev structure is used to describe PCI devices.
*/
@@ -106,6 +124,7 @@
this if your device has broken DMA
or supports 64-bit transfers. */
+ struct pci_dev_pm *pm; /* power management information */
pci_power_t current_state; /* Current operating state. In ACPI-speak,
this is D0-D3, D0 being fully functional,
and D3 being off. */
On Tue, Nov 15, 2005 at 10:31:17PM -0500, Adam Belay wrote:
> +int pci_setup_device_pm(struct pci_dev *dev)
Care to give kernel doc for this new function?
> +{
> + int pm;
> + u16 pmcsr, pmc;
> + struct pci_dev_pm *pm_data;
> +
> + pm = pci_find_capability(dev, PCI_CAP_ID_PM);
> + if (!pm) {
> + dev->current_state = PCI_D0;
> + return 0;
> + }
> +
> + pci_read_config_word(dev,pm + PCI_PM_PMC,&pmc);
> +
> + if ((pmc & PCI_PM_CAP_VER_MASK) > 3) {
> + printk(KERN_DEBUG
> + "PCI: %s has unsupported PM cap regs version (%u)\n",
> + pci_name(dev), pmc & PCI_PM_CAP_VER_MASK);
> + return -EIO;
> + }
> +
> + dev->pm = pm_data = kmalloc(sizeof(struct pci_dev_pm), GFP_KERNEL);
> + if (!pm_data)
> + return -ENOMEM;
> +
> + memset(pm_data, 0, sizeof(struct pci_dev_pm));
> +
> + pm_data->pm_offset = pm;
> +
> + /* determine supported device states */
> + /* all PM capable devices support at least D0 and D3 */
> + pm_data->state_mask |= ((1 << PCI_D0) | (1 << PCI_D3hot));
> + if (pmc & PCI_PM_CAP_D1)
> + pm_data->state_mask |= (1 << PCI_D1);
> + if (pmc & PCI_PM_CAP_D2)
> + pm_data->state_mask |= (1 << PCI_D2);
> +
> + /* PME capabilities */
> + pm_data->pme_mask = ((pmc & PCI_PM_CAP_PME_MASK)
> + >> (ffs(PCI_PM_CAP_PME_MASK) - 1));
> +
> + pci_read_config_word(dev, pm + PCI_PM_CTRL, &pmcsr);
> +
> + /* device context retention */
> + pm_data->dsi = (pmc & PCI_PM_CAP_DSI) ? 1 : 0;
> + pm_data->no_soft_reset = (pmcsr & PCI_PM_CTRL_NO_SOFT_RESET) ? 1 : 0;
> +
> + dev->current_state = (pmcsr & PCI_PM_CTRL_STATE_MASK);
> +
> + return 0;
> +}
> --- a/drivers/pci/probe.c 2005-11-07 08:08:00.000000000 -0500
> +++ b/drivers/pci/probe.c 2005-11-07 08:05:10.000000000 -0500
> @@ -601,8 +601,7 @@
> pr_debug("PCI: Found %s [%04x/%04x] %06x %02x\n", pci_name(dev),
> dev->vendor, dev->device, class, dev->hdr_type);
>
> - /* "Unknown power state" */
> - dev->current_state = PCI_UNKNOWN;
> + pci_setup_device_pm(dev);
>
> /* Early fixups, before probing the BARs */
> pci_fixup_device(pci_fixup_early, dev);
> --- a/include/linux/pci.h 2005-11-07 08:08:00.000000000 -0500
> +++ b/include/linux/pci.h 2005-11-07 08:02:55.000000000 -0500
> @@ -68,6 +68,18 @@
> #define DEVICE_COUNT_COMPATIBLE 4
> #define DEVICE_COUNT_RESOURCE 12
>
> +struct pci_dev_pm {
> + unsigned int pm_offset; /* the PCI PM capability offset */
> +
> + unsigned int dsi:1; /* vendor-specific initialization needed
> + after a reset */
> + unsigned int no_soft_reset:1; /* PCI config context retained when
> + going from D3_hot to D0 */
> +
> + unsigned char state_mask; /* a mask of supported power states */
> + unsigned char pme_mask; /* a mask of power states that allow #PME */
Trailing space, use quilt it strips this :)
> + struct pci_dev_pm *pm; /* power management information */
Why make this a pointer and not just part of this structure? Don't all
pci devices need this?
thanks,
greg k-h
On Tue, 2005-11-15 at 22:21 -0800, Greg KH wrote:
> On Tue, Nov 15, 2005 at 10:31:17PM -0500, Adam Belay wrote:
> > +int pci_setup_device_pm(struct pci_dev *dev)
>
> Care to give kernel doc for this new function?
Absolutely. I was planing to do this but must have forgotten.
> > + unsigned char state_mask; /* a mask of supported power states */
> > + unsigned char pme_mask; /* a mask of power states that allow #PME */
>
> Trailing space, use quilt it strips this :)
Sorry about that :)
>
> > + struct pci_dev_pm *pm; /* power management information */
>
> Why make this a pointer and not just part of this structure? Don't all
> pci devices need this?
Actually, not every PCI device supports the PCI PM spec. There are many
devices, even in modern systems, that can only be in D0. I was thinking
we could save some memory and allocate this structure when PCI PM is
detected. Would that be ok?
Thanks,
Adam
On Wed, Nov 16, 2005 at 02:21:01AM -0500, Adam Belay wrote:
> On Tue, 2005-11-15 at 22:21 -0800, Greg KH wrote:
> > On Tue, Nov 15, 2005 at 10:31:17PM -0500, Adam Belay wrote:
> > > +int pci_setup_device_pm(struct pci_dev *dev)
> >
> > Care to give kernel doc for this new function?
>
> Absolutely. I was planing to do this but must have forgotten.
>
> > > + unsigned char state_mask; /* a mask of supported power states */
> > > + unsigned char pme_mask; /* a mask of power states that allow #PME */
> >
> > Trailing space, use quilt it strips this :)
>
> Sorry about that :)
>
> >
> > > + struct pci_dev_pm *pm; /* power management information */
> >
> > Why make this a pointer and not just part of this structure? Don't all
> > pci devices need this?
>
> Actually, not every PCI device supports the PCI PM spec. There are many
> devices, even in modern systems, that can only be in D0. I was thinking
> we could save some memory and allocate this structure when PCI PM is
> detected. Would that be ok?
That would be ok, but you better remember to check for the pointer when
doing things like suspend and resume of the config space.
thanks,
greg k-h