Skip to contentSkip to conversation

PCI PM: capability probing and setup

RFC PATCH In review Adam Belay 2 participants 4 messages Nov 16, 2005 PCI

lore
Share & tools
lore

Adam Belay

author
#0 lore raw

RFC PATCH 2/6 PCI PM: capability probing and setup

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.

drivers/pci/pm.c +57 -0
@@ -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;
+}
drivers/pci/probe.c +1 -2
@@ -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);
include/linux/pci.h +19 -0
@@ -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. */
#1 lore raw
Quoted 1 line · Adam Belay

+int pci_setup_device_pm(struct pci_dev *dev)

Care to give kernel doc for this new function?

Quoted 68 lines
drivers/pci/pm.c +50 -0
+{
+	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;
+}
drivers/pci/probe.c +1 -2
@@ -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);
include/linux/pci.h +0 -0
@@ -68,6 +68,18 @@
 #define DEVICE_COUNT_COMPATIBLE	4
 #define DEVICE_COUNT_RESOURCE	12
 

include/linux/pci.h line 80

Quoted 10 lines
include/linux/pci.h +10 -0
+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 :)

include/linux/pci.h line 127

Quoted 1 line
include/linux/pci.h +1 -0
+	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

author
#2 lore raw
Quoted 4 lines · Greg KH
Quoted 1 line · Adam Belay

+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.

Quoted 4 lines

include/linux/pci.h line 80

Quoted 2 lines
include/linux/pci.h +2 -0
+	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 :)

Quoted 5 lines

include/linux/pci.h line 127

Quoted 2 lines
include/linux/pci.h +1 -0
+	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

#3 lore raw
Quoted 21 lines · Adam Belay
Quoted 4 lines · Greg KH
Quoted 1 line · Adam Belay

+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.

Quoted 4 lines

include/linux/pci.h line 80

Quoted 2 lines
include/linux/pci.h +2 -0
+	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 :)

Quoted 5 lines

include/linux/pci.h line 127

Quoted 2 lines
include/linux/pci.h +1 -0
+	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?

Quoted 4 lines

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