Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fd6852c8 authored by Benjamin Herrenschmidt's avatar Benjamin Herrenschmidt Committed by Paul Mackerras
Browse files

powerpc/pci: Fix various pseries PCI hotplug issues



The pseries PCI hotplug code has a number of issues, ranging from
incorrect resource setup to crashes, depending on what is added,
when, whether it contains a bridge, etc etc....

This fixes a whole bunch of these, while actually simplifying the code
a bit, using more generic code in the process and factoring out common
code between adding of a PHB, a slot or a device.

Signed-off-by: default avatarBenjamin Herrenschmidt <benh@kernel.crashing.org>
Signed-off-by: default avatarPaul Mackerras <paulus@samba.org>
parent b5ae5f91
Loading
Loading
Loading
Loading
+0 −3
Original line number Diff line number Diff line
@@ -241,9 +241,6 @@ extern void pcibios_remove_pci_devices(struct pci_bus *bus);

/** Discover new pci devices under this bus, and add them */
extern void pcibios_add_pci_devices(struct pci_bus *bus);
extern void pcibios_fixup_new_pci_devices(struct pci_bus *bus);

extern int pcibios_remove_root_bus(struct pci_controller *phb);

static inline struct pci_controller *pci_bus_to_host(const struct pci_bus *bus)
{
+2 −3
Original line number Diff line number Diff line
@@ -204,15 +204,14 @@ static inline struct resource *pcibios_select_root(struct pci_dev *pdev,
	return root;
}

extern void pcibios_setup_new_device(struct pci_dev *dev);

extern void pcibios_claim_one_bus(struct pci_bus *b);

extern void pcibios_allocate_bus_resources(struct pci_bus *bus);
extern void pcibios_finish_adding_to_bus(struct pci_bus *bus);

extern void pcibios_resource_survey(void);

extern struct pci_controller *init_phb_dynamic(struct device_node *dn);
extern int remove_phb_dynamic(struct pci_controller *phb);

extern struct pci_dev *of_create_pci_dev(struct device_node *node,
					struct pci_bus *bus, int devfn);
+37 −4
Original line number Diff line number Diff line
@@ -203,7 +203,7 @@ char __devinit *pcibios_setup(char *str)
	return str;
}

void __devinit pcibios_setup_new_device(struct pci_dev *dev)
static void __devinit pcibios_setup_new_device(struct pci_dev *dev)
{
	struct dev_archdata *sd = &dev->dev.archdata;

@@ -221,7 +221,6 @@ void __devinit pcibios_setup_new_device(struct pci_dev *dev)
	if (ppc_md.pci_dma_dev_setup)
		ppc_md.pci_dma_dev_setup(dev);
}
EXPORT_SYMBOL(pcibios_setup_new_device);

/*
 * Reads the interrupt pin to determine if interrupt is use by card.
@@ -1397,9 +1396,10 @@ void __init pcibios_resource_survey(void)

#ifdef CONFIG_HOTPLUG

/* This is used by the pSeries hotplug driver to allocate resource
/* This is used by the PCI hotplug driver to allocate resource
 * of newly plugged busses. We can try to consolidate with the
 * rest of the code later, for now, keep it as-is
 * rest of the code later, for now, keep it as-is as our main
 * resource allocation function doesn't deal with sub-trees yet.
 */
void __devinit pcibios_claim_one_bus(struct pci_bus *bus)
{
@@ -1414,6 +1414,14 @@ void __devinit pcibios_claim_one_bus(struct pci_bus *bus)

			if (r->parent || !r->start || !r->flags)
				continue;

			pr_debug("PCI: Claiming %s: "
				 "Resource %d: %016llx..%016llx [%x]\n",
				 pci_name(dev), i,
				 (unsigned long long)r->start,
				 (unsigned long long)r->end,
				 (unsigned int)r->flags);

			pci_claim_resource(dev, i);
		}
	}
@@ -1422,6 +1430,31 @@ void __devinit pcibios_claim_one_bus(struct pci_bus *bus)
		pcibios_claim_one_bus(child_bus);
}
EXPORT_SYMBOL_GPL(pcibios_claim_one_bus);


/* pcibios_finish_adding_to_bus
 *
 * This is to be called by the hotplug code after devices have been
 * added to a bus, this include calling it for a PHB that is just
 * being added
 */
void pcibios_finish_adding_to_bus(struct pci_bus *bus)
{
	pr_debug("PCI: Finishing adding to hotplug bus %04x:%02x\n",
		 pci_domain_nr(bus), bus->number);

	/* Allocate bus and devices resources */
	pcibios_allocate_bus_resources(bus);
	pcibios_claim_one_bus(bus);

	/* Add new devices to global lists.  Register in proc, sysfs. */
	pci_bus_add_devices(bus);

	/* Fixup EEH */
	eeh_add_device_tree_late(bus);
}
EXPORT_SYMBOL_GPL(pcibios_finish_adding_to_bus);

#endif /* CONFIG_HOTPLUG */

int pcibios_enable_device(struct pci_dev *dev, int mask)
+0 −48
Original line number Diff line number Diff line
@@ -301,51 +301,3 @@ void __init find_and_init_phbs(void)
#endif /* CONFIG_PPC32 */
	}
}

/* RPA-specific bits for removing PHBs */
int pcibios_remove_root_bus(struct pci_controller *phb)
{
	struct pci_bus *b = phb->bus;
	struct resource *res;
	int rc, i;

	res = b->resource[0];
	if (!res->flags) {
		printk(KERN_ERR "%s: no IO resource for PHB %s\n", __func__,
				b->name);
		return 1;
	}

	rc = pcibios_unmap_io_space(b);
	if (rc) {
		printk(KERN_ERR "%s: failed to unmap IO on bus %s\n",
			__func__, b->name);
		return 1;
	}

	if (release_resource(res)) {
		printk(KERN_ERR "%s: failed to release IO on bus %s\n",
				__func__, b->name);
		return 1;
	}

	for (i = 1; i < 3; ++i) {
		res = b->resource[i];
		if (!res->flags && i == 0) {
			printk(KERN_ERR "%s: no MEM resource for PHB %s\n",
				__func__, b->name);
			return 1;
		}
		if (res->flags && release_resource(res)) {
			printk(KERN_ERR
			       "%s: failed to release IO %d on bus %s\n",
				__func__, i, b->name);
			return 1;
		}
	}

	pcibios_free_controller(phb);

	return 0;
}
EXPORT_SYMBOL(pcibios_remove_root_bus);
+81 −82
Original line number Diff line number Diff line
@@ -25,6 +25,8 @@
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#undef DEBUG

#include <linux/pci.h>
#include <asm/pci-bridge.h>
#include <asm/ppc-pci.h>
@@ -69,74 +71,25 @@ EXPORT_SYMBOL_GPL(pcibios_find_pci_bus);
 * Remove all of the PCI devices under this bus both from the
 * linux pci device tree, and from the powerpc EEH address cache.
 */
void
pcibios_remove_pci_devices(struct pci_bus *bus)
void pcibios_remove_pci_devices(struct pci_bus *bus)
{
 	struct pci_dev *dev, *tmp;
	struct pci_bus *child_bus;

	/* First go down child busses */
	list_for_each_entry(child_bus, &bus->children, node)
		pcibios_remove_pci_devices(child_bus);

	pr_debug("PCI: Removing devices on bus %04x:%02x\n",
		 pci_domain_nr(bus),  bus->number);
	list_for_each_entry_safe(dev, tmp, &bus->devices, bus_list) {
		pr_debug("     * Removing %s...\n", pci_name(dev));
		eeh_remove_bus_device(dev);
 		pci_remove_bus_device(dev);
 	}
}
EXPORT_SYMBOL_GPL(pcibios_remove_pci_devices);

/* Must be called before pci_bus_add_devices */
void
pcibios_fixup_new_pci_devices(struct pci_bus *bus)
{
	struct pci_dev *dev;

	list_for_each_entry(dev, &bus->devices, bus_list) {
		/* Skip already-added devices */
		if (!dev->is_added) {
			int i;

			/* Fill device archdata and setup iommu table */
			pcibios_setup_new_device(dev);

			pci_read_irq_line(dev);
			for (i = 0; i < PCI_NUM_RESOURCES; i++) {
				struct resource *r = &dev->resource[i];

				if (r->parent || !r->start || !r->flags)
					continue;
				pci_claim_resource(dev, i);
			}
		}
	}
}
EXPORT_SYMBOL_GPL(pcibios_fixup_new_pci_devices);

static int
pcibios_pci_config_bridge(struct pci_dev *dev)
{
	u8 sec_busno;
	struct pci_bus *child_bus;

	/* Get busno of downstream bus */
	pci_read_config_byte(dev, PCI_SECONDARY_BUS, &sec_busno);

	/* Add to children of PCI bridge dev->bus */
	child_bus = pci_add_new_bus(dev->bus, dev, sec_busno);
	if (!child_bus) {
		printk (KERN_ERR "%s: could not add second bus\n", __func__);
		return -EIO;
	}
	sprintf(child_bus->name, "PCI Bus #%02x", child_bus->number);

	pci_scan_child_bus(child_bus);

	/* Fixup new pci devices */
	pcibios_fixup_new_pci_devices(child_bus);

	/* Make the discovered devices available */
	pci_bus_add_devices(child_bus);

	eeh_add_device_tree_late(child_bus);
	return 0;
}

/**
 * pcibios_add_pci_devices - adds new pci devices to bus
 *
@@ -147,10 +100,9 @@ pcibios_pci_config_bridge(struct pci_dev *dev)
 * is how this routine differs from other, similar pcibios
 * routines.)
 */
void
pcibios_add_pci_devices(struct pci_bus * bus)
void pcibios_add_pci_devices(struct pci_bus * bus)
{
	int slotno, num, mode;
	int slotno, num, mode, pass, max;
	struct pci_dev *dev;
	struct device_node *dn = pci_bus_to_OF_node(bus);

@@ -162,26 +114,23 @@ pcibios_add_pci_devices(struct pci_bus * bus)

	if (mode == PCI_PROBE_DEVTREE) {
		/* use ofdt-based probe */
		of_scan_bus(dn, bus);
		if (!list_empty(&bus->devices)) {
			pcibios_fixup_new_pci_devices(bus);
			pci_bus_add_devices(bus);
			eeh_add_device_tree_late(bus);
		}
		of_rescan_bus(dn, bus);
	} else if (mode == PCI_PROBE_NORMAL) {
		/* use legacy probe */
		slotno = PCI_SLOT(PCI_DN(dn->child)->devfn);
		num = pci_scan_slot(bus, PCI_DEVFN(slotno, 0));
		if (num) {
			pcibios_fixup_new_pci_devices(bus);
			pci_bus_add_devices(bus);
			eeh_add_device_tree_late(bus);
		if (!num)
			return;
		pcibios_setup_bus_devices(bus);
		max = bus->secondary;
		for (pass=0; pass < 2; pass++)
			list_for_each_entry(dev, &bus->devices, bus_list) {
			if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE ||
			    dev->hdr_type == PCI_HEADER_TYPE_CARDBUS)
				max = pci_scan_bridge(bus, dev, max, pass);
		}

		list_for_each_entry(dev, &bus->devices, bus_list)
			if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE)
				pcibios_pci_config_bridge(dev);
	}
	pcibios_finish_adding_to_bus(bus);
}
EXPORT_SYMBOL_GPL(pcibios_add_pci_devices);

@@ -190,6 +139,8 @@ struct pci_controller * __devinit init_phb_dynamic(struct device_node *dn)
	struct pci_controller *phb;
	int primary;

	pr_debug("PCI: Initializing new hotplug PHB %s\n", dn->full_name);

	primary = list_empty(&hose_list);
	phb = pcibios_alloc_controller(dn);
	if (!phb)
@@ -203,11 +154,59 @@ struct pci_controller * __devinit init_phb_dynamic(struct device_node *dn)
		eeh_add_device_tree_early(dn);

	scan_phb(phb);
	pcibios_allocate_bus_resources(phb->bus);
	pcibios_fixup_new_pci_devices(phb->bus);
	pci_bus_add_devices(phb->bus);
	eeh_add_device_tree_late(phb->bus);
	pcibios_finish_adding_to_bus(phb->bus);

	return phb;
}
EXPORT_SYMBOL_GPL(init_phb_dynamic);

/* RPA-specific bits for removing PHBs */
int remove_phb_dynamic(struct pci_controller *phb)
{
	struct pci_bus *b = phb->bus;
	struct resource *res;
	int rc, i;

	pr_debug("PCI: Removing PHB %04x:%02x... \n",
		 pci_domain_nr(b), b->number);

	/* We cannot to remove a root bus that has children */
	if (!(list_empty(&b->children) && list_empty(&b->devices)))
		return -EBUSY;

	/* We -know- there aren't any child devices anymore at this stage
	 * and thus, we can safely unmap the IO space as it's not in use
	 */
	res = &phb->io_resource;
	if (res->flags & IORESOURCE_IO) {
		rc = pcibios_unmap_io_space(b);
		if (rc) {
			printk(KERN_ERR "%s: failed to unmap IO on bus %s\n",
			       __func__, b->name);
			return 1;
		}
	}

	/* Unregister the bridge device from sysfs and remove the PCI bus */
	device_unregister(b->bridge);
	phb->bus = NULL;
	pci_remove_bus(b);

	/* Now release the IO resource */
	if (res->flags & IORESOURCE_IO)
		release_resource(res);

	/* Release memory resources */
	for (i = 0; i < 3; ++i) {
		res = &phb->mem_resources[i];
		if (!(res->flags & IORESOURCE_MEM))
			continue;
		release_resource(res);
	}

	/* Free pci_controller data structure */
	pcibios_free_controller(phb);

	return 0;
}
EXPORT_SYMBOL_GPL(remove_phb_dynamic);
Loading