[patch 2.6.12] pci: restore BAR values in pci_enable_device

John W. Linville
2005-07-01T02:26:37Z

[-- Attachment #1: Type: text/plain, Size: 2731 bytes --]

Some PCI devices 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 a device, so that the driver will be able to access it.

Acked-by: Adam Belay <ambx1@neo.rr.com>
Acked-by: Russell King <rmk@arm.linux.org.uk>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
---
Some firmware 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.

Since the fix should be safe for any device, it seems appropriate to
make it part of the PCI infrastructure.

drivers/pci/pci.c | 49 ++++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 48 insertions(+), 1 deletion(-)

diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -378,9 +378,56 @@ pci_restore_state(struct pci_dev *dev)
int
pci_enable_device_bars(struct pci_dev *dev, int bars)
{
- int err;
+ int i, numres, err;

pci_set_power_state(dev, PCI_D0);
+
+ /* Some devices lose PCI config header data during D3hot->D0
+ transition. Since some firmware leaves devices in D3hot
+ state at boot, this information needs to be restored. We
+ could force drivers to do this, but better to leave them
+ ignorant of PCI PM trivia...
+ */
+ switch (dev->hdr_type) {
+ case PCI_HEADER_TYPE_NORMAL:
+ numres = 6;
+ break;
+ case PCI_HEADER_TYPE_BRIDGE:
+ numres = 2;
+ break;
+ case PCI_HEADER_TYPE_CARDBUS:
+ numres = 1;
+ break;
+ default:
+ /* Should never get here, but just in case... */
+ numres = 0;
+ break;
+ }
+ for (i = 0; i < numres; i ++) {
+ struct pci_bus_region region;
+ u32 val;
+ int reg;
+
+ if (!dev->resource[i].flags)
+ continue;
+
+ pcibios_resource_to_bus(dev, &region, &dev->resource[i]);
+
+ val = region.start
+ | (dev->resource[i].flags & PCI_REGION_FLAG_MASK);
+
+ reg = PCI_BASE_ADDRESS_0 + (i * 4);
+
+ pci_write_config_dword(dev, reg, val);
+
+ if ((val & (PCI_BASE_ADDRESS_SPACE
+ | PCI_BASE_ADDRESS_MEM_TYPE_MASK))
+ == (PCI_BASE_ADDRESS_SPACE_MEMORY
+ | PCI_BASE_ADDRESS_MEM_TYPE_64)) {
+ pci_write_config_dword(dev, reg + 4, 0);
+ }
+ }
+
if ((err = pcibios_enable_device(dev, bars)) < 0)
return err;
return 0;
--
John W. Linville
linville@tuxdriver.com