[SPARC64]: Avoid JBUS errors on some Niagara systems.
authorDavid S. Miller <davem@sunset.davemloft.net>
Sat, 10 Jun 2006 08:06:25 +0000 (01:06 -0700)
committerDavid S. Miller <davem@sunset.davemloft.net>
Sat, 10 Jun 2006 08:06:25 +0000 (01:06 -0700)
Doing PCI config space accesses to non-present PCI slots
can result in fatal JBUS errors if the PCI config access
hypervisor call is performed on cpus other than the boot
cpu.

PCI config space accesses to present PCI slots works just
fine.

Recursively traverse the OBP device tree under the PCI
controller node and record all present device IDs into
a small hash table.

Avoid the hypervisor call for any PCI config space access
attempt for a device not recorded in the hash table.

Signed-off-by: David S. Miller <davem@davemloft.net>
arch/sparc64/kernel/pci_sun4v.c

index 2b7a1f316a937e56622e9241de9690c7c8f77c45..0c08952029707c846fb3e152a16d30edd42b453a 100644 (file)
@@ -599,18 +599,128 @@ struct pci_iommu_ops pci_sun4v_iommu_ops = {
 
 /* SUN4V PCI configuration space accessors. */
 
-static inline int pci_sun4v_out_of_range(struct pci_pbm_info *pbm, unsigned int bus, unsigned int device, unsigned int func)
+struct pdev_entry {
+       struct pdev_entry       *next;
+       u32                     devhandle;
+       unsigned int            bus;
+       unsigned int            device;
+       unsigned int            func;
+};
+
+#define PDEV_HTAB_SIZE 16
+#define PDEV_HTAB_MASK (PDEV_HTAB_SIZE - 1)
+static struct pdev_entry *pdev_htab[PDEV_HTAB_SIZE];
+
+static inline unsigned int pdev_hashfn(u32 devhandle, unsigned int bus, unsigned int device, unsigned int func)
 {
-       if (bus == pbm->pci_first_busno) {
-               if (device == 0 && func == 0)
-                       return 0;
-               return 1;
+       unsigned int val;
+
+       val = (devhandle ^ (devhandle >> 4));
+       val ^= bus;
+       val ^= device;
+       val ^= func;
+
+       return val & PDEV_HTAB_MASK;
+}
+
+static int pdev_htab_add(u32 devhandle, unsigned int bus, unsigned int device, unsigned int func)
+{
+       struct pdev_entry *p = kmalloc(sizeof(*p), GFP_KERNEL);
+       struct pdev_entry **slot;
+
+       if (!p)
+               return -ENOMEM;
+
+       slot = &pdev_htab[pdev_hashfn(devhandle, bus, device, func)];
+       p->next = *slot;
+       *slot = p;
+
+       p->devhandle = devhandle;
+       p->bus = bus;
+       p->device = device;
+       p->func = func;
+
+       return 0;
+}
+
+/* Recursively descend into the OBP device tree, rooted at toplevel_node,
+ * looking for a PCI device matching bus and devfn.
+ */
+static int obp_find(struct linux_prom_pci_registers *pregs, int toplevel_node, unsigned int bus, unsigned int devfn)
+{
+       toplevel_node = prom_getchild(toplevel_node);
+
+       while (toplevel_node != 0) {
+               int ret = obp_find(pregs, toplevel_node, bus, devfn);
+
+               if (ret != 0)
+                       return ret;
+
+               ret = prom_getproperty(toplevel_node, "reg", (char *) pregs,
+                                      sizeof(*pregs) * PROMREG_MAX);
+               if (ret == 0 || ret == -1)
+                       goto next_sibling;
+
+               if (((pregs[0].phys_hi >> 16) & 0xff) == bus &&
+                   ((pregs[0].phys_hi >> 8) & 0xff) == devfn)
+                       break;
+
+       next_sibling:
+               toplevel_node = prom_getsibling(toplevel_node);
+       }
+
+       return toplevel_node;
+}
+
+static int pdev_htab_populate(struct pci_pbm_info *pbm)
+{
+       struct linux_prom_pci_registers pr[PROMREG_MAX];
+       u32 devhandle = pbm->devhandle;
+       unsigned int bus;
+
+       for (bus = pbm->pci_first_busno; bus <= pbm->pci_last_busno; bus++) {
+               unsigned int devfn;
+
+               for (devfn = 0; devfn < 256; devfn++) {
+                       unsigned int device = PCI_SLOT(devfn);
+                       unsigned int func = PCI_FUNC(devfn);
+
+                       if (obp_find(pr, pbm->prom_node, bus, devfn)) {
+                               int err = pdev_htab_add(devhandle, bus,
+                                                       device, func);
+                               if (err)
+                                       return err;
+                       }
+               }
+       }
+
+       return 0;
+}
+
+static struct pdev_entry *pdev_find(u32 devhandle, unsigned int bus, unsigned int device, unsigned int func)
+{
+       struct pdev_entry *p;
+
+       p = pdev_htab[pdev_hashfn(devhandle, bus, device, func)];
+       while (p) {
+               if (p->devhandle == devhandle &&
+                   p->bus == bus &&
+                   p->device == device &&
+                   p->func == func)
+                       break;
+
+               p = p->next;
        }
 
+       return p;
+}
+
+static inline int pci_sun4v_out_of_range(struct pci_pbm_info *pbm, unsigned int bus, unsigned int device, unsigned int func)
+{
        if (bus < pbm->pci_first_busno ||
            bus > pbm->pci_last_busno)
                return 1;
-       return 0;
+       return pdev_find(pbm->devhandle, bus, device, func) == NULL;
 }
 
 static int pci_sun4v_read_pci_cfg(struct pci_bus *bus_dev, unsigned int devfn,
@@ -1063,6 +1173,8 @@ static void pci_sun4v_pbm_init(struct pci_controller_info *p, int prom_node, u32
 
        pci_sun4v_get_bus_range(pbm);
        pci_sun4v_iommu_init(pbm);
+
+       pdev_htab_populate(pbm);
 }
 
 void sun4v_pci_init(int node, char *model_name)