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

Commit 2b1e5978 authored by David S. Miller's avatar David S. Miller Committed by David S. Miller
Browse files

[SPARC64]: of_device layer IRQ resolution



Do IRQ determination generically by parsing the PROM properties,
and using IRQ controller drivers for final resolution.

One immediate positive effect is that all of the IRQ frobbing
in the EBUS, ISA, and PCI controller layers has been eliminated.
We just look up the of_device and use the properly computed
value.

The PCI controller irq_build() routines are gone and no longer
used.  Unfortunately sbus_build_irq() has to remain as there is
a direct reference to this in the sunzilog driver.  That can be
killed off once the sparc32 side of this is written and the
sunzilog driver is transformed into an "of" bus driver.

Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent c3a8b85f
Loading
Loading
Loading
Loading
+33 −117
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@
#include <asm/pbm.h>
#include <asm/ebus.h>
#include <asm/oplib.h>
#include <asm/prom.h>
#include <asm/of_device.h>
#include <asm/bpp.h>
#include <asm/irq.h>

@@ -279,45 +281,12 @@ static inline void *ebus_alloc(size_t size)
	return mem;
}

int __init ebus_intmap_match(struct linux_ebus *ebus,
			     struct linux_prom_registers *reg,
			     int *interrupt)
{
	struct linux_prom_ebus_intmap *imap;
	struct linux_prom_ebus_intmask *imask;
	unsigned int hi, lo, irq;
	int i, len, n_imap;

	imap = of_get_property(ebus->prom_node, "interrupt-map", &len);
	if (!imap)
		return 0;
	n_imap = len / sizeof(imap[0]);

	imask = of_get_property(ebus->prom_node, "interrupt-map-mask", NULL);
	if (!imask)
		return 0;

	hi = reg->which_io & imask->phys_hi;
	lo = reg->phys_addr & imask->phys_lo;
	irq = *interrupt & imask->interrupt;
	for (i = 0; i < n_imap; i++) {
		if ((imap[i].phys_hi == hi) &&
		    (imap[i].phys_lo == lo) &&
		    (imap[i].interrupt == irq)) {
			*interrupt = imap[i].cinterrupt;
			return 0;
		}
	}
	return -1;
}

void __init fill_ebus_child(struct device_node *dp,
			    struct linux_prom_registers *preg,
static void __init fill_ebus_child(struct device_node *dp,
				   struct linux_ebus_child *dev,
				   int non_standard_regs)
{
	struct of_device *op;
	int *regs;
	int *irqs;
	int i, len;

	dev->prom_node = dp;
@@ -354,12 +323,16 @@ void __init fill_ebus_child(struct device_node *dp,
		}
	}

	for (i = 0; i < PROMINTR_MAX; i++)
		dev->irqs[i] = PCI_IRQ_NONE;

	irqs = of_get_property(dp, "interrupts", &len);
	if (!irqs) {
	op = of_find_device_by_node(dp);
	if (!op) {
		dev->num_irqs = 0;
	} else {
		dev->num_irqs = op->num_irqs;
		for (i = 0; i < dev->num_irqs; i++)
			dev->irqs[i] = op->irqs[i];
	}

	if (!dev->num_irqs) {
		/*
		 * Oh, well, some PROMs don't export interrupts
		 * property to children of EBus devices...
@@ -375,23 +348,6 @@ void __init fill_ebus_child(struct device_node *dp,
				dev->irqs[0] = dev->parent->irqs[1];
			}
		}
	} else {
		dev->num_irqs = len / sizeof(irqs[0]);
		for (i = 0; i < dev->num_irqs; i++) {
			struct pci_pbm_info *pbm = dev->bus->parent;
			struct pci_controller_info *p = pbm->parent;

			if (ebus_intmap_match(dev->bus, preg, &irqs[i]) != -1) {
				dev->irqs[i] = p->irq_build(pbm,
							    dev->bus->self,
							    irqs[i]);
			} else {
				/* If we get a bogus interrupt property, just
				 * record the raw value instead of punting.
				 */
				dev->irqs[i] = irqs[i];
			}
		}
	}
}

@@ -403,72 +359,32 @@ static int __init child_regs_nonstandard(struct linux_ebus_device *dev)
	return 0;
}

void __init fill_ebus_device(struct device_node *dp, struct linux_ebus_device *dev)
static void __init fill_ebus_device(struct device_node *dp, struct linux_ebus_device *dev)
{
	struct linux_prom_registers *regs;
	struct linux_ebus_child *child;
	int *irqs;
	int i, n, len;
	struct of_device *op;
	int i, len;

	dev->prom_node = dp;

	printk(" [%s", dp->name);

	regs = of_get_property(dp, "reg", &len);
	if (!regs) {
	op = of_find_device_by_node(dp);
	if (!op) {
		dev->num_addrs = 0;
		goto probe_interrupts;
	}

	if (len % sizeof(struct linux_prom_registers)) {
		prom_printf("UGH: proplen for %s was %d, need multiple of %d\n",
			    dev->prom_node->name, len,
			    (int)sizeof(struct linux_prom_registers));
		prom_halt();
	}
		dev->num_irqs = 0;
	} else {
		(void) of_get_property(dp, "reg", &len);
		dev->num_addrs = len / sizeof(struct linux_prom_registers);

	for (i = 0; i < dev->num_addrs; i++) {
		/* XXX Learn how to interpret ebus ranges... -DaveM */
		if (regs[i].which_io >= 0x10)
			n = (regs[i].which_io - 0x10) >> 2;
		else
			n = regs[i].which_io;

		dev->resource[i].start  = dev->bus->self->resource[n].start;
		dev->resource[i].start += (unsigned long)regs[i].phys_addr;
		dev->resource[i].end    =
			(dev->resource[i].start + (unsigned long)regs[i].reg_size - 1UL);
		dev->resource[i].flags  = IORESOURCE_MEM;
		dev->resource[i].name   = dev->prom_node->name;
		request_resource(&dev->bus->self->resource[n],
				 &dev->resource[i]);
	}

probe_interrupts:
	for (i = 0; i < PROMINTR_MAX; i++)
		dev->irqs[i] = PCI_IRQ_NONE;
		for (i = 0; i < dev->num_addrs; i++)
			memcpy(&dev->resource[i],
			       &op->resource[i],
			       sizeof(struct resource));

	irqs = of_get_property(dp, "interrupts", &len);
	if (!irqs) {
		dev->num_irqs = 0;
	} else {
		dev->num_irqs = len / sizeof(irqs[0]);
		for (i = 0; i < dev->num_irqs; i++) {
			struct pci_pbm_info *pbm = dev->bus->parent;
			struct pci_controller_info *p = pbm->parent;

			if (ebus_intmap_match(dev->bus, &regs[0], &irqs[i]) != -1) {
				dev->irqs[i] = p->irq_build(pbm,
							    dev->bus->self,
							    irqs[i]);
			} else {
				/* If we get a bogus interrupt property, just
				 * record the raw value instead of punting.
				 */
				dev->irqs[i] = irqs[i];
			}
		}
		dev->num_irqs = op->num_irqs;
		for (i = 0; i < dev->num_irqs; i++)
			dev->irqs[i] = op->irqs[i];
	}

	dev->ofdev.node = dp;
@@ -490,7 +406,7 @@ void __init fill_ebus_device(struct device_node *dp, struct linux_ebus_device *d
		child->next = NULL;
		child->parent = dev;
		child->bus = dev->bus;
		fill_ebus_child(dp, regs, child,
		fill_ebus_child(dp, child,
				child_regs_nonstandard(dev));

		while ((dp = dp->sibling) != NULL) {
@@ -500,7 +416,7 @@ void __init fill_ebus_device(struct device_node *dp, struct linux_ebus_device *d
			child->next = NULL;
			child->parent = dev;
			child->bus = dev->bus;
			fill_ebus_child(dp, regs, child,
			fill_ebus_child(dp, child,
					child_regs_nonstandard(dev));
		}
	}
+6 −95
Original line number Diff line number Diff line
@@ -3,6 +3,8 @@
#include <linux/pci.h>
#include <linux/slab.h>
#include <asm/oplib.h>
#include <asm/prom.h>
#include <asm/of_device.h>
#include <asm/isa.h>

struct sparc_isa_bridge *isa_chain;
@@ -46,107 +48,16 @@ isa_dev_get_resource(struct sparc_isa_device *isa_dev)
	return pregs;
}

/* I can't believe they didn't put a real INO in the isa device
 * interrupts property.  The whole point of the OBP properties
 * is to shield the kernel from IRQ routing details.
 *
 * The P1275 standard for ISA devices seems to also have been
 * totally ignored.
 *
 * On later systems, an interrupt-map and interrupt-map-mask scheme
 * akin to EBUS is used.
 */
static struct {
	int	obp_irq;
	int	pci_ino;
} grover_irq_table[] = {
	{ 1, 0x00 },	/* dma, unknown ino at this point */
	{ 2, 0x27 },	/* floppy */
	{ 3, 0x22 },	/* parallel */
	{ 4, 0x2b },	/* serial */
	{ 5, 0x25 },	/* acpi power management */

	{ 0, 0x00 }	/* end of table */
};

static int __init isa_dev_get_irq_using_imap(struct sparc_isa_device *isa_dev,
					     struct sparc_isa_bridge *isa_br,
					     int *interrupt,
					     struct linux_prom_registers *reg)
{
	struct linux_prom_ebus_intmap *imap;
	struct linux_prom_ebus_intmask *imask;
	unsigned int hi, lo, irq;
	int i, len, n_imap;

	imap = of_get_property(isa_br->prom_node, "interrupt-map", &len);
	if (!imap)
		return 0;
	n_imap = len / sizeof(imap[0]);

	imask = of_get_property(isa_br->prom_node, "interrupt-map-mask", NULL);
	if (!imask)
		return 0;

	hi = reg->which_io & imask->phys_hi;
	lo = reg->phys_addr & imask->phys_lo;
	irq = *interrupt & imask->interrupt;
	for (i = 0; i < n_imap; i++) {
		if ((imap[i].phys_hi == hi) &&
		    (imap[i].phys_lo == lo) &&
		    (imap[i].interrupt == irq)) {
			*interrupt = imap[i].cinterrupt;
			return 0;
		}
	}
	return -1;
}

static void __init isa_dev_get_irq(struct sparc_isa_device *isa_dev,
				   struct linux_prom_registers *pregs)
{
	int irq_prop;
	struct of_device *op = of_find_device_by_node(isa_dev->prom_node);

	irq_prop = of_getintprop_default(isa_dev->prom_node,
					 "interrupts", -1);
	if (irq_prop <= 0) {
		goto no_irq;
	if (!op || !op->num_irqs) {
		isa_dev->irq = PCI_IRQ_NONE;
	} else {
		struct pci_controller_info *pcic;
		struct pci_pbm_info *pbm;
		int i;

		if (of_find_property(isa_dev->bus->prom_node,
				     "interrupt-map", NULL)) {
			if (!isa_dev_get_irq_using_imap(isa_dev,
							isa_dev->bus,
							&irq_prop,
							pregs))
				goto route_irq;
		isa_dev->irq = op->irqs[0];
	}

		for (i = 0; grover_irq_table[i].obp_irq != 0; i++) {
			if (grover_irq_table[i].obp_irq == irq_prop) {
				int ino = grover_irq_table[i].pci_ino;

				if (ino == 0)
					goto no_irq;
 
				irq_prop = ino;
				goto route_irq;
			}
		}
		goto no_irq;

route_irq:
		pbm = isa_dev->bus->parent;
		pcic = pbm->parent;
		isa_dev->irq = pcic->irq_build(pbm, NULL, irq_prop);
		return;
	}

no_irq:
	isa_dev->irq = PCI_IRQ_NONE;
}

static void __init isa_fill_children(struct sparc_isa_device *parent_isa_dev)
+188 −6
Original line number Diff line number Diff line
@@ -146,6 +146,26 @@ void of_iounmap(void __iomem *base, unsigned long size)
}
EXPORT_SYMBOL(of_iounmap);

static int node_match(struct device *dev, void *data)
{
	struct of_device *op = to_of_device(dev);
	struct device_node *dp = data;

	return (op->node == dp);
}

struct of_device *of_find_device_by_node(struct device_node *dp)
{
	struct device *dev = bus_find_device(&of_bus_type, NULL,
					     dp, node_match);

	if (dev)
		return to_of_device(dev);

	return NULL;
}
EXPORT_SYMBOL(of_find_device_by_node);

#ifdef CONFIG_PCI
struct bus_type isa_bus_type = {
       .name	= "isa",
@@ -261,7 +281,6 @@ static unsigned int of_bus_default_get_flags(u32 *addr)
	return IORESOURCE_MEM;
}


/*
 * PCI bus specific translator
 */
@@ -594,12 +613,171 @@ static void __init build_device_resources(struct of_device *op,
	}
}

static struct device_node * __init
apply_interrupt_map(struct device_node *dp, struct device_node *pp,
		    u32 *imap, int imlen, u32 *imask,
		    unsigned int *irq_p)
{
	struct device_node *cp;
	unsigned int irq = *irq_p;
	struct of_bus *bus;
	phandle handle;
	u32 *reg;
	int na, num_reg, i;

	bus = of_match_bus(pp);
	bus->count_cells(dp, &na, NULL);

	reg = of_get_property(dp, "reg", &num_reg);
	if (!reg || !num_reg)
		return NULL;

	imlen /= ((na + 3) * 4);
	handle = 0;
	for (i = 0; i < imlen; i++) {
		int j;

		for (j = 0; j < na; j++) {
			if ((reg[j] & imask[j]) != imap[j])
				goto next;
		}
		if (imap[na] == irq) {
			handle = imap[na + 1];
			irq = imap[na + 2];
			break;
		}

	next:
		imap += (na + 3);
	}
	if (i == imlen)
		return NULL;

	*irq_p = irq;
	cp = of_find_node_by_phandle(handle);

	return cp;
}

static unsigned int __init pci_irq_swizzle(struct device_node *dp,
					   struct device_node *pp,
					   unsigned int irq)
{
	struct linux_prom_pci_registers *regs;
	unsigned int devfn, slot, ret;

	if (irq < 1 || irq > 4)
		return irq;

	regs = of_get_property(dp, "reg", NULL);
	if (!regs)
		return irq;

	devfn = (regs->phys_hi >> 8) & 0xff;
	slot = (devfn >> 3) & 0x1f;

	ret = ((irq - 1 + (slot & 3)) & 3) + 1;

	return ret;
}

static unsigned int __init build_one_device_irq(struct of_device *op,
						struct device *parent,
						unsigned int irq)
{
	struct device_node *dp = op->node;
	struct device_node *pp, *ip;
	unsigned int orig_irq = irq;

	if (irq == 0xffffffff)
		return irq;

	if (dp->irq_trans) {
		irq = dp->irq_trans->irq_build(dp, irq,
					       dp->irq_trans->data);
#if 1
		printk("%s: direct translate %x --> %x\n",
		       dp->full_name, orig_irq, irq);
#endif
		return irq;
	}

	/* Something more complicated.  Walk up to the root, applying
	 * interrupt-map or bus specific translations, until we hit
	 * an IRQ translator.
	 *
	 * If we hit a bus type or situation we cannot handle, we
	 * stop and assume that the original IRQ number was in a
	 * format which has special meaning to it's immediate parent.
	 */
	pp = dp->parent;
	ip = NULL;
	while (pp) {
		void *imap, *imsk;
		int imlen;

		imap = of_get_property(pp, "interrupt-map", &imlen);
		imsk = of_get_property(pp, "interrupt-map-mask", NULL);
		if (imap && imsk) {
			struct device_node *iret;
			int this_orig_irq = irq;

			iret = apply_interrupt_map(dp, pp,
						   imap, imlen, imsk,
						   &irq);
#if 1
			printk("%s: Apply [%s:%x] imap --> [%s:%x]\n",
			       op->node->full_name,
			       pp->full_name, this_orig_irq,
			       (iret ? iret->full_name : "NULL"), irq);
#endif
			if (!iret)
				break;

			if (iret->irq_trans) {
				ip = iret;
				break;
			}
		} else {
			if (!strcmp(pp->type, "pci") ||
			    !strcmp(pp->type, "pciex")) {
				unsigned int this_orig_irq = irq;

				irq = pci_irq_swizzle(dp, pp, irq);
#if 1
				printk("%s: PCI swizzle [%s] %x --> %x\n",
				       op->node->full_name,
				       pp->full_name, this_orig_irq, irq);
#endif
			}

			if (pp->irq_trans) {
				ip = pp;
				break;
			}
		}
		dp = pp;
		pp = pp->parent;
	}
	if (!ip)
		return orig_irq;

	irq = ip->irq_trans->irq_build(op->node, irq,
				       ip->irq_trans->data);
#if 1
	printk("%s: Apply IRQ trans [%s] %x --> %x\n",
	       op->node->full_name, ip->full_name, orig_irq, irq);
#endif

	return irq;
}

static struct of_device * __init scan_one_device(struct device_node *dp,
						 struct device *parent)
{
	struct of_device *op = kzalloc(sizeof(*op), GFP_KERNEL);
	unsigned int *irq;
	int len;
	int len, i;

	if (!op)
		return NULL;
@@ -613,12 +791,16 @@ static struct of_device * __init scan_one_device(struct device_node *dp,
		op->portid = of_getintprop_default(dp, "portid", -1);

	irq = of_get_property(dp, "interrupts", &len);
	if (irq)
		op->irq = *irq;
	else
		op->irq = 0xffffffff;
	if (irq) {
		memcpy(op->irqs, irq, len);
		op->num_irqs = len / 4;
	} else {
		op->num_irqs = 0;
	}

	build_device_resources(op, parent);
	for (i = 0; i < op->num_irqs; i++)
		op->irqs[i] = build_one_device_irq(op, parent, op->irqs[i]);

	op->dev.parent = parent;
	op->dev.bus = &of_bus_type;
+0 −6
Original line number Diff line number Diff line
@@ -404,14 +404,8 @@ void pcibios_bus_to_resource(struct pci_dev *pdev, struct resource *res,
}
EXPORT_SYMBOL(pcibios_bus_to_resource);

extern int pci_irq_verbose;

char * __init pcibios_setup(char *str)
{
	if (!strcmp(str, "irq_verbose")) {
		pci_irq_verbose = 1;
		return NULL;
	}
	return str;
}

+6 −285
Original line number Diff line number Diff line
@@ -10,12 +10,10 @@

#include <asm/pbm.h>
#include <asm/prom.h>
#include <asm/of_device.h>

#include "pci_impl.h"

/* Pass "pci=irq_verbose" on the kernel command line to enable this.  */
int pci_irq_verbose;

/* Fix self device of BUS and hook it into BUS->self.
 * The pci_scan_bus does not do this for the host bridge.
 */
@@ -169,6 +167,7 @@ static void __init pdev_cookie_fillin(struct pci_pbm_info *pbm,
	}
	pcp->pbm = pbm;
	pcp->prom_node = dp;
	pcp->op = of_find_device_by_node(dp);
	memcpy(pcp->prom_regs, pregs,
	       nregs * sizeof(struct linux_prom_pci_registers));
	pcp->num_prom_regs = nregs;
@@ -549,296 +548,18 @@ void __init pci_assign_unassigned(struct pci_pbm_info *pbm,
		pci_assign_unassigned(pbm, bus);
}

static inline unsigned int pci_slot_swivel(struct pci_pbm_info *pbm,
					   struct pci_dev *toplevel_pdev,
					   struct pci_dev *pdev,
					   unsigned int interrupt)
{
	unsigned int ret;

	if (unlikely(interrupt < 1 || interrupt > 4)) {
		printk("%s: Device %s interrupt value of %u is strange.\n",
		       pbm->name, pci_name(pdev), interrupt);
		return interrupt;
	}

	ret = ((interrupt - 1 + (PCI_SLOT(pdev->devfn) & 3)) & 3) + 1;

	if (pci_irq_verbose)
		printk("%s: %s IRQ Swivel %s [%x:%x] -> [%x]\n",
		       pbm->name, pci_name(toplevel_pdev), pci_name(pdev),
		       interrupt, PCI_SLOT(pdev->devfn), ret);

	return ret;
}

static inline unsigned int pci_apply_intmap(struct pci_pbm_info *pbm,
					    struct pci_dev *toplevel_pdev,
					    struct pci_dev *pbus,
					    struct pci_dev *pdev,
					    unsigned int interrupt,
					    struct device_node **cnode)
{
	struct linux_prom_pci_intmap *imap;
	struct linux_prom_pci_intmask *imask;
	struct pcidev_cookie *pbus_pcp = pbus->sysdata;
	struct pcidev_cookie *pdev_pcp = pdev->sysdata;
	struct linux_prom_pci_registers *pregs = pdev_pcp->prom_regs;
	struct property *prop;
	int plen, num_imap, i;
	unsigned int hi, mid, lo, irq, orig_interrupt;

	*cnode = pbus_pcp->prom_node;

	prop = of_find_property(pbus_pcp->prom_node, "interrupt-map", &plen);
	if (!prop ||
	    (plen % sizeof(struct linux_prom_pci_intmap)) != 0) {
		printk("%s: Device %s interrupt-map has bad len %d\n",
		       pbm->name, pci_name(pbus), plen);
		goto no_intmap;
	}
	imap = prop->value;
	num_imap = plen / sizeof(struct linux_prom_pci_intmap);

	prop = of_find_property(pbus_pcp->prom_node, "interrupt-map-mask", &plen);
	if (!prop ||
	    (plen % sizeof(struct linux_prom_pci_intmask)) != 0) {
		printk("%s: Device %s interrupt-map-mask has bad len %d\n",
		       pbm->name, pci_name(pbus), plen);
		goto no_intmap;
	}
	imask = prop->value;

	orig_interrupt = interrupt;

	hi   = pregs->phys_hi & imask->phys_hi;
	mid  = pregs->phys_mid & imask->phys_mid;
	lo   = pregs->phys_lo & imask->phys_lo;
	irq  = interrupt & imask->interrupt;

	for (i = 0; i < num_imap; i++) {
		if (imap[i].phys_hi  == hi   &&
		    imap[i].phys_mid == mid  &&
		    imap[i].phys_lo  == lo   &&
		    imap[i].interrupt == irq) {
			*cnode = of_find_node_by_phandle(imap[i].cnode);
			interrupt = imap[i].cinterrupt;
		}
	}

	if (pci_irq_verbose)
		printk("%s: %s MAP BUS %s DEV %s [%x] -> [%x]\n",
		       pbm->name, pci_name(toplevel_pdev),
		       pci_name(pbus), pci_name(pdev),
		       orig_interrupt, interrupt);

no_intmap:
	return interrupt;
}

/* For each PCI bus on the way to the root:
 * 1) If it has an interrupt-map property, apply it.
 * 2) Else, swivel the interrupt number based upon the PCI device number.
 *
 * Return the "IRQ controller" node.  If this is the PBM's device node,
 * all interrupt translations are complete, else we should use that node's
 * "reg" property to apply the PBM's "interrupt-{map,mask}" to the interrupt.
 */
static struct device_node * __init
pci_intmap_match_to_root(struct pci_pbm_info *pbm,
			 struct pci_dev *pdev,
			 unsigned int *interrupt)
{
	struct pci_dev *toplevel_pdev = pdev;
	struct pcidev_cookie *toplevel_pcp = toplevel_pdev->sysdata;
	struct device_node *cnode = toplevel_pcp->prom_node;

	while (pdev->bus->number != pbm->pci_first_busno) {
		struct pci_dev *pbus = pdev->bus->self;
		struct pcidev_cookie *pcp = pbus->sysdata;
		struct property *prop;

		prop = of_find_property(pcp->prom_node, "interrupt-map", NULL);
		if (!prop) {
			*interrupt = pci_slot_swivel(pbm, toplevel_pdev,
						     pdev, *interrupt);
			cnode = pcp->prom_node;
		} else {
			*interrupt = pci_apply_intmap(pbm, toplevel_pdev,
						      pbus, pdev,
						      *interrupt, &cnode);

			while (pcp->prom_node != cnode &&
			       pbus->bus->number != pbm->pci_first_busno) {
				pbus = pbus->bus->self;
				pcp = pbus->sysdata;
			}
		}
		pdev = pbus;

		if (cnode == pbm->prom_node)
			break;
	}

	return cnode;
}

static int __init pci_intmap_match(struct pci_dev *pdev, unsigned int *interrupt)
{
	struct pcidev_cookie *dev_pcp = pdev->sysdata;
	struct pci_pbm_info *pbm = dev_pcp->pbm;
	struct linux_prom_pci_registers *reg;
	struct device_node *cnode;
	struct property *prop;
	unsigned int hi, mid, lo, irq;
	int i, plen;

	cnode = pci_intmap_match_to_root(pbm, pdev, interrupt);
	if (cnode == pbm->prom_node)
		goto success;

	prop = of_find_property(cnode, "reg", &plen);
	if (!prop ||
	    (plen % sizeof(struct linux_prom_pci_registers)) != 0) {
		printk("%s: OBP node %s reg property has bad len %d\n",
		       pbm->name, cnode->full_name, plen);
		goto fail;
	}
	reg = prop->value;

	hi   = reg[0].phys_hi & pbm->pbm_intmask->phys_hi;
	mid  = reg[0].phys_mid & pbm->pbm_intmask->phys_mid;
	lo   = reg[0].phys_lo & pbm->pbm_intmask->phys_lo;
	irq  = *interrupt & pbm->pbm_intmask->interrupt;

	for (i = 0; i < pbm->num_pbm_intmap; i++) {
		struct linux_prom_pci_intmap *intmap;

		intmap = &pbm->pbm_intmap[i];

		if (intmap->phys_hi  == hi  &&
		    intmap->phys_mid == mid &&
		    intmap->phys_lo  == lo  &&
		    intmap->interrupt == irq) {
			*interrupt = intmap->cinterrupt;
			goto success;
		}
	}

fail:
	return 0;

success:
	if (pci_irq_verbose)
		printk("%s: Routing bus[%2x] slot[%2x] to INO[%02x]\n",
		       pbm->name,
		       pdev->bus->number, PCI_SLOT(pdev->devfn),
		       *interrupt);
	return 1;
}

static void __init pdev_fixup_irq(struct pci_dev *pdev)
{
	struct pcidev_cookie *pcp = pdev->sysdata;
	struct pci_pbm_info *pbm = pcp->pbm;
	struct pci_controller_info *p = pbm->parent;
	unsigned int portid = pbm->portid;
	unsigned int prom_irq;
	struct device_node *dp = pcp->prom_node;
	struct property *prop;
	struct of_device *op = pcp->op;

	/* If this is an empty EBUS device, sometimes OBP fails to
	 * give it a valid fully specified interrupts property.
	 * The EBUS hooked up to SunHME on PCI I/O boards of
	 * Ex000 systems is one such case.
	 *
	 * The interrupt is not important so just ignore it.
	 */
	if (pdev->vendor == PCI_VENDOR_ID_SUN &&
	    pdev->device == PCI_DEVICE_ID_SUN_EBUS &&
	    !dp->child) {
		pdev->irq = 0;
	if (op->irqs[0] == 0xffffffff) {
		pdev->irq = PCI_IRQ_NONE;
		return;
	}

	prop = of_find_property(dp, "interrupts", NULL);
	if (!prop) {
		pdev->irq = 0;
		return;
	}
	prom_irq = *(unsigned int *) prop->value;

	if (tlb_type != hypervisor) {
		/* Fully specified already? */
		if (((prom_irq & PCI_IRQ_IGN) >> 6) == portid) {
			pdev->irq = p->irq_build(pbm, pdev, prom_irq);
			goto have_irq;
		}

		/* An onboard device? (bit 5 set) */
		if ((prom_irq & PCI_IRQ_INO) & 0x20) {
			pdev->irq = p->irq_build(pbm, pdev, (portid << 6 | prom_irq));
			goto have_irq;
		}
	}

	/* Can we find a matching entry in the interrupt-map? */
	if (pci_intmap_match(pdev, &prom_irq)) {
		pdev->irq = p->irq_build(pbm, pdev, (portid << 6) | prom_irq);
		goto have_irq;
	}

	/* Ok, we have to do it the hard way. */
	{
		unsigned int bus, slot, line;

		bus = (pbm == &pbm->parent->pbm_B) ? (1 << 4) : 0;

		/* If we have a legal interrupt property, use it as
		 * the IRQ line.
		 */
		if (prom_irq > 0 && prom_irq < 5) {
			line = ((prom_irq - 1) & 3);
		} else {
			u8 pci_irq_line;

			/* Else just directly consult PCI config space. */
			pci_read_config_byte(pdev, PCI_INTERRUPT_PIN, &pci_irq_line);
			line = ((pci_irq_line - 1) & 3);
		}

		/* Now figure out the slot.
		 *
		 * Basically, device number zero on the top-level bus is
		 * always the PCI host controller.  Slot 0 is then device 1.
		 * PBM A supports two external slots (0 and 1), and PBM B
		 * supports 4 external slots (0, 1, 2, and 3).  On-board PCI
		 * devices are wired to device numbers outside of these
		 * ranges. -DaveM
 		 */
		if (pdev->bus->number == pbm->pci_first_busno) {
			slot = PCI_SLOT(pdev->devfn) - pbm->pci_first_slot;
		} else {
			struct pci_dev *bus_dev;

			/* Underneath a bridge, use slot number of parent
			 * bridge which is closest to the PBM.
			 */
			bus_dev = pdev->bus->self;
			while (bus_dev->bus &&
			       bus_dev->bus->number != pbm->pci_first_busno)
				bus_dev = bus_dev->bus->self;

			slot = PCI_SLOT(bus_dev->devfn) - pbm->pci_first_slot;
		}
		slot = slot << 2;

		pdev->irq = p->irq_build(pbm, pdev,
					 ((portid << 6) & PCI_IRQ_IGN) |
					 (bus | slot | line));
	}
	pdev->irq = op->irqs[0];

have_irq:
	pci_write_config_byte(pdev, PCI_INTERRUPT_LINE,
			      pdev->irq & PCI_IRQ_INO);
}
Loading