diff options
author | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2011-04-11 11:37:07 +1000 |
---|---|---|
committer | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2011-06-08 09:08:17 +1000 |
commit | 98d9f30c820d509145757e6ecbc36013aa02f7bc (patch) | |
tree | dd5da915d991352ced56ed849612029339f64198 /drivers | |
parent | 1fa7b6a29c61358cc2ca6f64cef4aa0e1a7ca74c (diff) |
pci/of: Match PCI devices to OF nodes dynamically
powerpc has two different ways of matching PCI devices to their
corresponding OF node (if any) for historical reasons. The ppc64 one
does a scan looking for matching bus/dev/fn, while the ppc32 one does a
scan looking only for matching dev/fn on each level in order to be
agnostic to busses being renumbered (which Linux does on some
platforms).
This removes both and instead moves the matching code to the PCI core
itself. It's the most logical place to do it: when a pci_dev is created,
we know the parent and thus can do a single level scan for the matching
device_node (if any).
The benefit is that all archs now get the matching for free. There's one
hook the arch might want to provide to match a PHB bus to its device
node. A default weak implementation is provided that looks for the
parent device device node, but it's not entirely reliable on powerpc for
various reasons so powerpc provides its own.
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Acked-by: Michal Simek <monstr@monstr.eu>
Acked-by: Jesse Barnes <jbarnes@virtuousgeek.org>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/of/Kconfig | 8 | ||||
-rw-r--r-- | drivers/of/Makefile | 1 | ||||
-rw-r--r-- | drivers/of/of_pci.c | 112 | ||||
-rw-r--r-- | drivers/of/of_pci_irq.c | 92 | ||||
-rw-r--r-- | drivers/pci/Makefile | 2 | ||||
-rw-r--r-- | drivers/pci/hotplug/rpadlpar_core.c | 2 | ||||
-rw-r--r-- | drivers/pci/of.c | 61 | ||||
-rw-r--r-- | drivers/pci/probe.c | 7 |
8 files changed, 200 insertions, 85 deletions
diff --git a/drivers/of/Kconfig b/drivers/of/Kconfig index d06a6374ed6..cac63c9f49a 100644 --- a/drivers/of/Kconfig +++ b/drivers/of/Kconfig @@ -71,8 +71,14 @@ config OF_MDIO config OF_PCI def_tristate PCI - depends on PCI && (PPC || MICROBLAZE || X86) + depends on PCI help OpenFirmware PCI bus accessors +config OF_PCI_IRQ + def_tristate PCI + depends on OF_PCI && OF_IRQ + help + OpenFirmware PCI IRQ routing helpers + endmenu # OF diff --git a/drivers/of/Makefile b/drivers/of/Makefile index f7861ed2f28..dccb1176be5 100644 --- a/drivers/of/Makefile +++ b/drivers/of/Makefile @@ -10,3 +10,4 @@ obj-$(CONFIG_OF_NET) += of_net.o obj-$(CONFIG_OF_SPI) += of_spi.o obj-$(CONFIG_OF_MDIO) += of_mdio.o obj-$(CONFIG_OF_PCI) += of_pci.o +obj-$(CONFIG_OF_PCI_IRQ) += of_pci_irq.o diff --git a/drivers/of/of_pci.c b/drivers/of/of_pci.c index ac1ec54e4fd..ec7b060ae95 100644 --- a/drivers/of/of_pci.c +++ b/drivers/of/of_pci.c @@ -1,92 +1,40 @@ #include <linux/kernel.h> #include <linux/of_pci.h> -#include <linux/of_irq.h> #include <asm/prom.h> -/** - * of_irq_map_pci - Resolve the interrupt for a PCI device - * @pdev: the device whose interrupt is to be resolved - * @out_irq: structure of_irq filled by this function - * - * This function resolves the PCI interrupt for a given PCI device. If a - * device-node exists for a given pci_dev, it will use normal OF tree - * walking. If not, it will implement standard swizzling and walk up the - * PCI tree until an device-node is found, at which point it will finish - * resolving using the OF tree walking. - */ -int of_irq_map_pci(struct pci_dev *pdev, struct of_irq *out_irq) +static inline int __of_pci_pci_compare(struct device_node *node, + unsigned int devfn) { - struct device_node *dn, *ppnode; - struct pci_dev *ppdev; - u32 lspec; - __be32 lspec_be; - __be32 laddr[3]; - u8 pin; - int rc; + unsigned int size; + const __be32 *reg = of_get_property(node, "reg", &size); - /* Check if we have a device node, if yes, fallback to standard - * device tree parsing - */ - dn = pci_device_to_OF_node(pdev); - if (dn) { - rc = of_irq_map_one(dn, 0, out_irq); - if (!rc) - return rc; - } - - /* Ok, we don't, time to have fun. Let's start by building up an - * interrupt spec. we assume #interrupt-cells is 1, which is standard - * for PCI. If you do different, then don't use that routine. - */ - rc = pci_read_config_byte(pdev, PCI_INTERRUPT_PIN, &pin); - if (rc != 0) - return rc; - /* No pin, exit */ - if (pin == 0) - return -ENODEV; - - /* Now we walk up the PCI tree */ - lspec = pin; - for (;;) { - /* Get the pci_dev of our parent */ - ppdev = pdev->bus->self; - - /* Ouch, it's a host bridge... */ - if (ppdev == NULL) { - ppnode = pci_bus_to_OF_node(pdev->bus); - - /* No node for host bridge ? give up */ - if (ppnode == NULL) - return -EINVAL; - } else { - /* We found a P2P bridge, check if it has a node */ - ppnode = pci_device_to_OF_node(ppdev); - } - - /* Ok, we have found a parent with a device-node, hand over to - * the OF parsing code. - * We build a unit address from the linux device to be used for - * resolution. Note that we use the linux bus number which may - * not match your firmware bus numbering. - * Fortunately, in most cases, interrupt-map-mask doesn't - * include the bus number as part of the matching. - * You should still be careful about that though if you intend - * to rely on this function (you ship a firmware that doesn't - * create device nodes for all PCI devices). - */ - if (ppnode) - break; + if (!reg || size < 5 * sizeof(__be32)) + return 0; + return ((be32_to_cpup(®[0]) >> 8) & 0xff) == devfn; +} - /* We can only get here if we hit a P2P bridge with no node, - * let's do standard swizzling and try again +struct device_node *of_pci_find_child_device(struct device_node *parent, + unsigned int devfn) +{ + struct device_node *node, *node2; + + for_each_child_of_node(parent, node) { + if (__of_pci_pci_compare(node, devfn)) + return node; + /* + * Some OFs create a parent node "multifunc-device" as + * a fake root for all functions of a multi-function + * device we go down them as well. */ - lspec = pci_swizzle_interrupt_pin(pdev, lspec); - pdev = ppdev; + if (!strcmp(node->name, "multifunc-device")) { + for_each_child_of_node(node, node2) { + if (__of_pci_pci_compare(node2, devfn)) { + of_node_put(node); + return node2; + } + } + } } - - lspec_be = cpu_to_be32(lspec); - laddr[0] = cpu_to_be32((pdev->bus->number << 16) | (pdev->devfn << 8)); - laddr[1] = laddr[2] = cpu_to_be32(0); - return of_irq_map_raw(ppnode, &lspec_be, 1, laddr, out_irq); + return NULL; } -EXPORT_SYMBOL_GPL(of_irq_map_pci); +EXPORT_SYMBOL_GPL(of_pci_find_child_device); diff --git a/drivers/of/of_pci_irq.c b/drivers/of/of_pci_irq.c new file mode 100644 index 00000000000..ac1ec54e4fd --- /dev/null +++ b/drivers/of/of_pci_irq.c @@ -0,0 +1,92 @@ +#include <linux/kernel.h> +#include <linux/of_pci.h> +#include <linux/of_irq.h> +#include <asm/prom.h> + +/** + * of_irq_map_pci - Resolve the interrupt for a PCI device + * @pdev: the device whose interrupt is to be resolved + * @out_irq: structure of_irq filled by this function + * + * This function resolves the PCI interrupt for a given PCI device. If a + * device-node exists for a given pci_dev, it will use normal OF tree + * walking. If not, it will implement standard swizzling and walk up the + * PCI tree until an device-node is found, at which point it will finish + * resolving using the OF tree walking. + */ +int of_irq_map_pci(struct pci_dev *pdev, struct of_irq *out_irq) +{ + struct device_node *dn, *ppnode; + struct pci_dev *ppdev; + u32 lspec; + __be32 lspec_be; + __be32 laddr[3]; + u8 pin; + int rc; + + /* Check if we have a device node, if yes, fallback to standard + * device tree parsing + */ + dn = pci_device_to_OF_node(pdev); + if (dn) { + rc = of_irq_map_one(dn, 0, out_irq); + if (!rc) + return rc; + } + + /* Ok, we don't, time to have fun. Let's start by building up an + * interrupt spec. we assume #interrupt-cells is 1, which is standard + * for PCI. If you do different, then don't use that routine. + */ + rc = pci_read_config_byte(pdev, PCI_INTERRUPT_PIN, &pin); + if (rc != 0) + return rc; + /* No pin, exit */ + if (pin == 0) + return -ENODEV; + + /* Now we walk up the PCI tree */ + lspec = pin; + for (;;) { + /* Get the pci_dev of our parent */ + ppdev = pdev->bus->self; + + /* Ouch, it's a host bridge... */ + if (ppdev == NULL) { + ppnode = pci_bus_to_OF_node(pdev->bus); + + /* No node for host bridge ? give up */ + if (ppnode == NULL) + return -EINVAL; + } else { + /* We found a P2P bridge, check if it has a node */ + ppnode = pci_device_to_OF_node(ppdev); + } + + /* Ok, we have found a parent with a device-node, hand over to + * the OF parsing code. + * We build a unit address from the linux device to be used for + * resolution. Note that we use the linux bus number which may + * not match your firmware bus numbering. + * Fortunately, in most cases, interrupt-map-mask doesn't + * include the bus number as part of the matching. + * You should still be careful about that though if you intend + * to rely on this function (you ship a firmware that doesn't + * create device nodes for all PCI devices). + */ + if (ppnode) + break; + + /* We can only get here if we hit a P2P bridge with no node, + * let's do standard swizzling and try again + */ + lspec = pci_swizzle_interrupt_pin(pdev, lspec); + pdev = ppdev; + } + + lspec_be = cpu_to_be32(lspec); + laddr[0] = cpu_to_be32((pdev->bus->number << 16) | (pdev->devfn << 8)); + laddr[1] = laddr[2] = cpu_to_be32(0); + return of_irq_map_raw(ppnode, &lspec_be, 1, laddr, out_irq); +} +EXPORT_SYMBOL_GPL(of_irq_map_pci); diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile index c85f744270a..f27f4a1488a 100644 --- a/drivers/pci/Makefile +++ b/drivers/pci/Makefile @@ -70,4 +70,6 @@ obj-$(CONFIG_PCI_STUB) += pci-stub.o obj-$(CONFIG_XEN_PCIDEV_FRONTEND) += xen-pcifront.o +obj-$(CONFIG_OF) += of.o + ccflags-$(CONFIG_PCI_DEBUG) := -DDEBUG diff --git a/drivers/pci/hotplug/rpadlpar_core.c b/drivers/pci/hotplug/rpadlpar_core.c index 083034710fa..1d002b1c2bf 100644 --- a/drivers/pci/hotplug/rpadlpar_core.c +++ b/drivers/pci/hotplug/rpadlpar_core.c @@ -158,7 +158,7 @@ static void dlpar_pci_add_bus(struct device_node *dn) /* Scan below the new bridge */ if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE || dev->hdr_type == PCI_HEADER_TYPE_CARDBUS) - of_scan_pci_bridge(dn, dev); + of_scan_pci_bridge(dev); /* Map IO space for child bus, which may or may not succeed */ pcibios_map_io_space(dev->subordinate); diff --git a/drivers/pci/of.c b/drivers/pci/of.c new file mode 100644 index 00000000000..c94d37ec55c --- /dev/null +++ b/drivers/pci/of.c @@ -0,0 +1,61 @@ +/* + * PCI <-> OF mapping helpers + * + * Copyright 2011 IBM Corp. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/of.h> +#include <linux/of_pci.h> +#include "pci.h" + +void pci_set_of_node(struct pci_dev *dev) +{ + if (!dev->bus->dev.of_node) + return; + dev->dev.of_node = of_pci_find_child_device(dev->bus->dev.of_node, + dev->devfn); +} + +void pci_release_of_node(struct pci_dev *dev) +{ + of_node_put(dev->dev.of_node); + dev->dev.of_node = NULL; +} + +void pci_set_bus_of_node(struct pci_bus *bus) +{ + if (bus->self == NULL) + bus->dev.of_node = pcibios_get_phb_of_node(bus); + else + bus->dev.of_node = of_node_get(bus->self->dev.of_node); +} + +void pci_release_bus_of_node(struct pci_bus *bus) +{ + of_node_put(bus->dev.of_node); + bus->dev.of_node = NULL; +} + +struct device_node * __weak pcibios_get_phb_of_node(struct pci_bus *bus) +{ + /* This should only be called for PHBs */ + if (WARN_ON(bus->self || bus->parent)) + return NULL; + + /* Look for a node pointer in either the intermediary device we + * create above the root bus or it's own parent. Normally only + * the later is populated. + */ + if (bus->bridge->of_node) + return of_node_get(bus->bridge->of_node); + if (bus->bridge->parent->of_node) + return of_node_get(bus->bridge->parent->of_node); + return NULL; +} diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 48849ffdd67..c28c7b91910 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -52,6 +52,7 @@ static void release_pcibus_dev(struct device *dev) if (pci_bus->bridge) put_device(pci_bus->bridge); pci_bus_remove_resources(pci_bus); + pci_release_bus_of_node(pci_bus); kfree(pci_bus); } @@ -588,7 +589,7 @@ static struct pci_bus *pci_alloc_child_bus(struct pci_bus *parent, child->self = bridge; child->bridge = get_device(&bridge->dev); - + pci_set_bus_of_node(child); pci_set_bus_speed(child); /* Set up default resource pointers and names.. */ @@ -1038,6 +1039,7 @@ static void pci_release_dev(struct device *dev) pci_dev = to_pci_dev(dev); pci_release_capabilities(pci_dev); + pci_release_of_node(pci_dev); kfree(pci_dev); } @@ -1157,6 +1159,8 @@ static struct pci_dev *pci_scan_device(struct pci_bus *bus, int devfn) dev->vendor = l & 0xffff; dev->device = (l >> 16) & 0xffff; + pci_set_of_node(dev); + if (pci_setup_device(dev)) { kfree(dev); return NULL; @@ -1409,6 +1413,7 @@ struct pci_bus * pci_create_bus(struct device *parent, goto dev_reg_err; b->bridge = get_device(dev); device_enable_async_suspend(b->bridge); + pci_set_bus_of_node(b); if (!parent) set_dev_node(b->bridge, pcibus_to_node(b)); |