[SPARC64]: Decode virtual-devices interrupts correctly.

Need to translate through the interrupt-map{,-mask] properties.

Signed-off-by: David S. Miller <davem@davemloft.net>
diff --git a/arch/sparc64/kernel/devices.c b/arch/sparc64/kernel/devices.c
index 71eee39..1341b99 100644
--- a/arch/sparc64/kernel/devices.c
+++ b/arch/sparc64/kernel/devices.c
@@ -22,6 +22,7 @@
 #include <asm/timer.h>
 #include <asm/cpudata.h>
 #include <asm/vdev.h>
+#include <asm/irq.h>
 
 /* Used to synchronize acceses to NatSemi SUPER I/O chip configure
  * operations in asm/ns87303.h
@@ -33,14 +34,28 @@
 
 u32 sun4v_vdev_devhandle;
 int sun4v_vdev_root;
-struct linux_prom_pci_intmap *sun4v_vdev_intmap;
-int sun4v_vdev_num_intmap;
-struct linux_prom_pci_intmap sun4v_vdev_intmask;
+
+struct vdev_intmap {
+	unsigned int phys;
+	unsigned int irq;
+	unsigned int cnode;
+	unsigned int cinterrupt;
+};
+
+struct vdev_intmask {
+	unsigned int phys;
+	unsigned int interrupt;
+	unsigned int __unused;
+};
+
+static struct vdev_intmap *vdev_intmap;
+static int vdev_num_intmap;
+static struct vdev_intmask vdev_intmask;
 
 static void __init sun4v_virtual_device_probe(void)
 {
 	struct linux_prom64_registers regs;
-	struct linux_prom_pci_intmap *ip;
+	struct vdev_intmap *ip;
 	int node, sz, err;
 
 	if (tlb_type != hypervisor)
@@ -58,10 +73,21 @@
 	prom_getproperty(node, "reg", (char *)&regs, sizeof(regs));
 	sun4v_vdev_devhandle = (regs.phys_addr >> 32UL) & 0x0fffffff;
 
-	sz = sizeof(*ip) * 64;
-	sun4v_vdev_intmap = ip = alloc_bootmem_low_pages(sz);
-	if (!sun4v_vdev_intmap) {
-		prom_printf("SUN4V: Error, cannot allocate vdev intmap.\n");
+	sz = prom_getproplen(node, "interrupt-map");
+	if (sz <= 0) {
+		prom_printf("SUN4V: Error, no vdev interrupt-map.\n");
+		prom_halt();
+	}
+
+	if ((sz % sizeof(*ip)) != 0) {
+		prom_printf("SUN4V: Bogus interrupt-map property size %d\n",
+			    sz);
+		prom_halt();
+	}
+
+	vdev_intmap = ip = alloc_bootmem_low_pages(sz);
+	if (!vdev_intmap) {
+		prom_printf("SUN4V: Error, cannot allocate vdev_intmap.\n");
 		prom_halt();
 	}
 
@@ -70,22 +96,70 @@
 		prom_printf("SUN4V: Fatal error, no vdev interrupt-map.\n");
 		prom_halt();
 	}
+	if (err != sz) {
+		prom_printf("SUN4V: Inconsistent interrupt-map size, "
+			    "proplen(%d) vs getprop(%d).\n", sz,err);
+		prom_halt();
+	}
 
-	sun4v_vdev_num_intmap = err / sizeof(*ip);
+	vdev_num_intmap = err / sizeof(*ip);
 
 	err = prom_getproperty(node, "interrupt-map-mask",
-			       (char *) &sun4v_vdev_intmask,
-			       sizeof(sun4v_vdev_intmask));
-	if (err == -1) {
+			       (char *) &vdev_intmask,
+			       sizeof(vdev_intmask));
+	if (err <= 0) {
 		prom_printf("SUN4V: Fatal error, no vdev "
 			    "interrupt-map-mask.\n");
 		prom_halt();
 	}
+	if (err % sizeof(vdev_intmask)) {
+		prom_printf("SUN4V: Bogus interrupt-map-mask "
+			    "property size %d\n", err);
+		prom_halt();
+	}
 
 	printk("SUN4V: virtual-devices devhandle[%x]\n",
 	       sun4v_vdev_devhandle);
 }
 
+unsigned int sun4v_vdev_device_interrupt(unsigned int dev_node)
+{
+	unsigned int irq, reg;
+	int err, i;
+
+	err = prom_getproperty(dev_node, "interrupts",
+			       (char *) &irq, sizeof(irq));
+	if (err <= 0) {
+		printk("VDEV: Cannot get \"interrupts\" "
+		       "property for OBP node %x\n", dev_node);
+		return 0;
+	}
+
+	err = prom_getproperty(dev_node, "reg",
+			       (char *) &reg, sizeof(reg));
+	if (err <= 0) {
+		printk("VDEV: Cannot get \"reg\" "
+		       "property for OBP node %x\n", dev_node);
+		return 0;
+	}
+
+	for (i = 0; i < vdev_num_intmap; i++) {
+		if (vdev_intmap[i].phys == (reg & vdev_intmask.phys) &&
+		    vdev_intmap[i].irq == (irq & vdev_intmask.interrupt)) {
+			irq = vdev_intmap[i].cinterrupt;
+			break;
+		}
+	}
+
+	if (i == vdev_num_intmap) {
+		printk("VDEV: No matching interrupt map entry "
+		       "for OBP node %x\n", dev_node);
+		return 0;
+	}
+
+	return sun4v_build_irq(sun4v_vdev_devhandle, irq, 4, 0);
+}
+
 static const char *cpu_mid_prop(void)
 {
 	if (tlb_type == spitfire)