PCI PM: capability probing and setup
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. */
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
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
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