[PATCH] ppc64: Save & restore of PCI device BARS
authorLinas Vepstas <linas@linas.org>
Fri, 4 Nov 2005 00:50:17 +0000 (18:50 -0600)
committerPaul Mackerras <paulus@samba.org>
Thu, 10 Nov 2005 00:38:14 +0000 (11:38 +1100)
14-eeh-device-bar-save.patch

After a PCI device has been resest, the device BAR's and other config
space info must be restored to the same state as they were in when
the firmware first handed us this device.  This will allow the
PCI device driver, when restarted, to correctly recognize and set up
the device.

Tis patch saves the device config space as early as reasonable after
the firmware has handed over the device.  Te state resore funcion
is inteded for use by the EEH recovery routines.

Signed-off-by: Linas Vepstas <linas@austin.ibm.com>
Signed-off-by: Paul Mackerras <paulus@samba.org>
arch/powerpc/platforms/pseries/eeh.c
include/asm-powerpc/ppc-pci.h

index a06c91a4852bcab6014547808d7d11266592f4c3..b760836bb9d105f5168eb7861b2e4de450c15762 100644 (file)
@@ -77,6 +77,9 @@
  */
 #define EEH_MAX_FAILS  100000
 
+/* Misc forward declaraions */
+static void eeh_save_bars(struct pci_dev * pdev, struct pci_dn *pdn);
+
 /* RTAS tokens */
 static int ibm_set_eeh_option;
 static int ibm_set_slot_reset;
@@ -366,6 +369,7 @@ static void pci_addr_cache_remove_device(struct pci_dev *dev)
  */
 void __init pci_addr_cache_build(void)
 {
+       struct device_node *dn;
        struct pci_dev *dev = NULL;
 
        if (!eeh_subsystem_enabled)
@@ -379,6 +383,10 @@ void __init pci_addr_cache_build(void)
                        continue;
                }
                pci_addr_cache_insert_device(dev);
+
+               /* Save the BAR's; firmware doesn't restore these after EEH reset */
+               dn = pci_device_to_OF_node(dev);
+               eeh_save_bars(dev, PCI_DN(dn));
        }
 
 #ifdef DEBUG
@@ -775,6 +783,108 @@ rtas_set_slot_reset(struct pci_dn *pdn)
        }
 }
 
+/* ------------------------------------------------------- */
+/** Save and restore of PCI BARs
+ *
+ * Although firmware will set up BARs during boot, it doesn't
+ * set up device BAR's after a device reset, although it will,
+ * if requested, set up bridge configuration. Thus, we need to
+ * configure the PCI devices ourselves.  
+ */
+
+/**
+ * __restore_bars - Restore the Base Address Registers
+ * Loads the PCI configuration space base address registers,
+ * the expansion ROM base address, the latency timer, and etc.
+ * from the saved values in the device node.
+ */
+static inline void __restore_bars (struct pci_dn *pdn)
+{
+       int i;
+
+       if (NULL==pdn->phb) return;
+       for (i=4; i<10; i++) {
+               rtas_write_config(pdn, i*4, 4, pdn->config_space[i]);
+       }
+
+       /* 12 == Expansion ROM Address */
+       rtas_write_config(pdn, 12*4, 4, pdn->config_space[12]);
+
+#define BYTE_SWAP(OFF) (8*((OFF)/4)+3-(OFF))
+#define SAVED_BYTE(OFF) (((u8 *)(pdn->config_space))[BYTE_SWAP(OFF)])
+
+       rtas_write_config (pdn, PCI_CACHE_LINE_SIZE, 1,
+                   SAVED_BYTE(PCI_CACHE_LINE_SIZE));
+
+       rtas_write_config (pdn, PCI_LATENCY_TIMER, 1,
+                   SAVED_BYTE(PCI_LATENCY_TIMER));
+
+       /* max latency, min grant, interrupt pin and line */
+       rtas_write_config(pdn, 15*4, 4, pdn->config_space[15]);
+}
+
+/**
+ * eeh_restore_bars - restore the PCI config space info
+ *
+ * This routine performs a recursive walk to the children
+ * of this device as well.
+ */
+void eeh_restore_bars(struct pci_dn *pdn)
+{
+       struct device_node *dn;
+       if (!pdn) 
+               return;
+       
+       if (! pdn->eeh_is_bridge)
+               __restore_bars (pdn);
+
+       dn = pdn->node->child;
+       while (dn) {
+               eeh_restore_bars (PCI_DN(dn));
+               dn = dn->sibling;
+       }
+}
+
+/**
+ * eeh_save_bars - save device bars
+ *
+ * Save the values of the device bars. Unlike the restore
+ * routine, this routine is *not* recursive. This is because
+ * PCI devices are added individuallly; but, for the restore,
+ * an entire slot is reset at a time.
+ */
+static void eeh_save_bars(struct pci_dev * pdev, struct pci_dn *pdn)
+{
+       int i;
+
+       if (!pdev || !pdn )
+               return;
+       
+       for (i = 0; i < 16; i++)
+               pci_read_config_dword(pdev, i * 4, &pdn->config_space[i]);
+
+       if (pdev->hdr_type == PCI_HEADER_TYPE_BRIDGE)
+               pdn->eeh_is_bridge = 1;
+}
+
+void
+rtas_configure_bridge(struct pci_dn *pdn)
+{
+       int token = rtas_token ("ibm,configure-bridge");
+       int rc;
+
+       if (token == RTAS_UNKNOWN_SERVICE)
+               return;
+       rc = rtas_call(token,3,1, NULL,
+                      pdn->eeh_config_addr,
+                      BUID_HI(pdn->phb->buid),
+                      BUID_LO(pdn->phb->buid));
+       if (rc) {
+               printk (KERN_WARNING "EEH: Unable to configure device bridge (%d) for %s\n",
+                       rc, pdn->node->full_name);
+       }
+}
+
 /* ------------------------------------------------------------- */
 /* The code below deals with enabling EEH for devices during  the
  * early boot sequence.  EEH must be enabled before any PCI probing
@@ -977,6 +1087,7 @@ EXPORT_SYMBOL_GPL(eeh_add_device_early);
 void eeh_add_device_late(struct pci_dev *dev)
 {
        struct device_node *dn;
+       struct pci_dn *pdn;
 
        if (!dev || !eeh_subsystem_enabled)
                return;
@@ -987,9 +1098,11 @@ void eeh_add_device_late(struct pci_dev *dev)
 
        pci_dev_get (dev);
        dn = pci_device_to_OF_node(dev);
-       PCI_DN(dn)->pcidev = dev;
+       pdn = PCI_DN(dn);
+       pdn->pcidev = dev;
 
        pci_addr_cache_insert_device (dev);
+       eeh_save_bars(dev, pdn);
 }
 EXPORT_SYMBOL_GPL(eeh_add_device_late);
 
index 930a606b4e3572f0ba89887a1e3cce2597ba9c52..d86c47872beab4f1ab1cd71fffc719875c91c3f2 100644 (file)
@@ -64,6 +64,29 @@ extern int pci_read_irq_line(struct pci_dev *pci_dev);
  */
 void rtas_set_slot_reset (struct pci_dn *);
 
+/** 
+ * eeh_restore_bars - Restore device configuration info.
+ *
+ * A reset of a PCI device will clear out its config space.
+ * This routines will restore the config space for this
+ * device, and is children, to values previously obtained
+ * from the firmware.
+ */
+void eeh_restore_bars(struct pci_dn *);
+
+/**
+ * rtas_configure_bridge -- firmware initialization of pci bridge
+ *
+ * Ask the firmware to configure all PCI bridges devices
+ * located behind the indicated node. Required after a
+ * pci device reset. Does essentially the same hing as
+ * eeh_restore_bars, but for brdges, and lets firmware 
+ * do the work.
+ */
+void rtas_configure_bridge(struct pci_dn *);
+
+int rtas_write_config(struct pci_dn *, int where, int size, u32 val);
+
 #endif
 
 #endif /* _ASM_POWERPC_PPC_PCI_H */