[PATCH] PCI: restore BAR values after D3hot->D0 for devices that need it

Some PCI devices (e.g. 3c905B, 3c556B) lose all configuration
(including BARs) when transitioning from D3hot->D0. This leaves such
a device in an inaccessible state. The patch below causes the BARs
to be restored when enabling such a device, so that its driver will
be able to access it.

The patch also adds pci_restore_bars as a new global symbol, and adds a
correpsonding EXPORT_SYMBOL_GPL for that.

Some firmware (e.g. Thinkpad T21) leaves devices in D3hot after a
(re)boot. Most drivers call pci_enable_device very early, so devices
left in D3hot that lose configuration during the D3hot->D0 transition
will be inaccessible to their drivers.

Drivers could be modified to account for this, but it would
be difficult to know which drivers need modification. This is
especially true since often many devices are covered by the same
driver. It likely would be necessary to replicate code across dozens
of drivers.

The patch below should trigger only when transitioning from D3hot->D0
(or at boot), and only for devices that have the "no soft reset" bit
cleared in the PM control register. I believe it is safe to include
this patch as part of the PCI infrastructure.

The cleanest implementation of pci_restore_bars was to call
pci_update_resource. Unfortunately, that does not currently exist
for the sparc64 architecture. The patch below includes a null
implemenation of pci_update_resource for sparc64.

Some have expressed interest in making general use of the the
pci_restore_bars function, so that has been exported to GPL licensed
modules.

Signed-off-by: John W. Linville <linville@tuxdriver.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>

authored by John W. Linville and committed by Linus Torvalds fec59a71 c3068951

+65 -5
+6
arch/sparc64/kernel/pci.c
··· 413 413 return -EBUSY; 414 414 } 415 415 416 + void pci_update_resource(struct pci_dev *dev, struct resource *res, int resno) 417 + { 418 + /* Not implemented for sparc64... */ 419 + BUG(); 420 + } 421 + 416 422 int pci_assign_resource(struct pci_dev *pdev, int resource) 417 423 { 418 424 struct pcidev_cookie *pcp = pdev->sysdata;
+55 -4
drivers/pci/pci.c
··· 222 222 } 223 223 224 224 /** 225 + * pci_restore_bars - restore a devices BAR values (e.g. after wake-up) 226 + * @dev: PCI device to have its BARs restored 227 + * 228 + * Restore the BAR values for a given device, so as to make it 229 + * accessible by its driver. 230 + */ 231 + void 232 + pci_restore_bars(struct pci_dev *dev) 233 + { 234 + int i, numres; 235 + 236 + switch (dev->hdr_type) { 237 + case PCI_HEADER_TYPE_NORMAL: 238 + numres = 6; 239 + break; 240 + case PCI_HEADER_TYPE_BRIDGE: 241 + numres = 2; 242 + break; 243 + case PCI_HEADER_TYPE_CARDBUS: 244 + numres = 1; 245 + break; 246 + default: 247 + /* Should never get here, but just in case... */ 248 + return; 249 + } 250 + 251 + for (i = 0; i < numres; i ++) 252 + pci_update_resource(dev, &dev->resource[i], i); 253 + } 254 + 255 + /** 225 256 * pci_set_power_state - Set the power state of a PCI device 226 257 * @dev: PCI device to be suspended 227 258 * @state: PCI power state (D0, D1, D2, D3hot, D3cold) we're entering ··· 270 239 int 271 240 pci_set_power_state(struct pci_dev *dev, pci_power_t state) 272 241 { 273 - int pm; 242 + int pm, need_restore = 0; 274 243 u16 pmcsr, pmc; 275 244 276 245 /* bound the state we're entering */ ··· 309 278 return -EIO; 310 279 } 311 280 281 + pci_read_config_word(dev, pm + PCI_PM_CTRL, &pmcsr); 282 + 312 283 /* If we're in D3, force entire word to 0. 313 284 * This doesn't affect PME_Status, disables PME_En, and 314 285 * sets PowerState to 0. 315 286 */ 316 - if (dev->current_state >= PCI_D3hot) 287 + if (dev->current_state >= PCI_D3hot) { 288 + if (!(pmcsr & PCI_PM_CTRL_NO_SOFT_RESET)) 289 + need_restore = 1; 317 290 pmcsr = 0; 318 - else { 319 - pci_read_config_word(dev, pm + PCI_PM_CTRL, &pmcsr); 291 + } else { 320 292 pmcsr &= ~PCI_PM_CTRL_STATE_MASK; 321 293 pmcsr |= state; 322 294 } ··· 342 308 platform_pci_set_power_state(dev, state); 343 309 344 310 dev->current_state = state; 311 + 312 + /* According to section 5.4.1 of the "PCI BUS POWER MANAGEMENT 313 + * INTERFACE SPECIFICATION, REV. 1.2", a device transitioning 314 + * from D3hot to D0 _may_ perform an internal reset, thereby 315 + * going to "D0 Uninitialized" rather than "D0 Initialized". 316 + * For example, at least some versions of the 3c905B and the 317 + * 3c556B exhibit this behaviour. 318 + * 319 + * At least some laptop BIOSen (e.g. the Thinkpad T21) leave 320 + * devices in a D3hot state at boot. Consequently, we need to 321 + * restore at least the BARs so that the device will be 322 + * accessible to its driver. 323 + */ 324 + if (need_restore) 325 + pci_restore_bars(dev); 326 + 345 327 return 0; 346 328 } 347 329 ··· 855 805 EXPORT_SYMBOL(isa_bridge); 856 806 #endif 857 807 808 + EXPORT_SYMBOL_GPL(pci_restore_bars); 858 809 EXPORT_SYMBOL(pci_enable_device_bars); 859 810 EXPORT_SYMBOL(pci_enable_device); 860 811 EXPORT_SYMBOL(pci_disable_device);
+1 -1
drivers/pci/setup-res.c
··· 26 26 #include "pci.h" 27 27 28 28 29 - static void 29 + void 30 30 pci_update_resource(struct pci_dev *dev, struct resource *res, int resno) 31 31 { 32 32 struct pci_bus_region region;
+3
include/linux/pci.h
··· 225 225 #define PCI_PM_CAP_PME_D3cold 0x8000 /* PME# from D3 (cold) */ 226 226 #define PCI_PM_CTRL 4 /* PM control and status register */ 227 227 #define PCI_PM_CTRL_STATE_MASK 0x0003 /* Current power state (D0 to D3) */ 228 + #define PCI_PM_CTRL_NO_SOFT_RESET 0x0004 /* No reset for D3hot->D0 */ 228 229 #define PCI_PM_CTRL_PME_ENABLE 0x0100 /* PME pin enable */ 229 230 #define PCI_PM_CTRL_DATA_SEL_MASK 0x1e00 /* Data select (??) */ 230 231 #define PCI_PM_CTRL_DATA_SCALE_MASK 0x6000 /* Data scale (??) */ ··· 817 816 void pci_clear_mwi(struct pci_dev *dev); 818 817 int pci_set_dma_mask(struct pci_dev *dev, u64 mask); 819 818 int pci_set_consistent_dma_mask(struct pci_dev *dev, u64 mask); 819 + void pci_update_resource(struct pci_dev *dev, struct resource *res, int resno); 820 820 int pci_assign_resource(struct pci_dev *dev, int i); 821 + void pci_restore_bars(struct pci_dev *dev); 821 822 822 823 /* ROM control related routines */ 823 824 void __iomem *pci_map_rom(struct pci_dev *pdev, size_t *size);