[PARISC] Truncate overlapping PAT PDC reported ranges

Deal with overlapping LBA MMIO resources,

rp3440 PDC BUG: PDC reports lmmio range for the last rope that overlaps
with the CPU HPA. Console output was:

...
Found devices:
1. Storm Peak Fast at 0xfffffffffe798000 [152] { 0, 0x0, 0x889, 0x00004 }
2. Storm Peak Fast at 0xfffffffffe799000 [153] { 0, 0x0, 0x889, 0x00004 }
...
FAILED: lba_fixup_bus() request for lmmio_space
[fffffffff0000000/fffffffffecffffe]

Output is now:

LBA: Truncating lmmio_space [fffffffff0000000/fffffffffecffffe] to
[fffffffff0000000,fffffffffe797fff]

My only concern with this patch is how C8000 (PAT PDC) will report
elmmio ranges when a gfx card is installed. I'll have to test this
another day.

Signed-off-by: Grant Grundler <grundler@parisc-linux.org>
Signed-off-by: James Bottomley <jejb@parisc-linux.org>
Signed-off-by: Matthew Wilcox <willy@parisc-linux.org>
Signed-off-by: Kyle McMartin <kyle@parisc-linux.org>
diff --git a/drivers/parisc/lba_pci.c b/drivers/parisc/lba_pci.c
index 4f6bdf0..cbae8c8 100644
--- a/drivers/parisc/lba_pci.c
+++ b/drivers/parisc/lba_pci.c
@@ -695,12 +695,72 @@
 		}
 	}
 }
-#else
-#define lba_claim_dev_resources(dev)
-#endif
 
 
 /*
+ * truncate_pat_collision:  Deal with overlaps or outright collisions
+ *			between PAT PDC reported ranges.
+ *
+ *   Broken PA8800 firmware will report lmmio range that
+ *   overlaps with CPU HPA. Just truncate the lmmio range.
+ *
+ *   BEWARE: conflicts with this lmmio range may be an
+ *   elmmio range which is pointing down another rope.
+ *
+ *  FIXME: only deals with one collision per range...theoretically we
+ *  could have several. Supporting more than one collision will get messy.
+ */
+static unsigned long
+truncate_pat_collision(struct resource *root, struct resource *new)
+{
+	unsigned long start = new->start;
+	unsigned long end = new->end;
+	struct resource *tmp = root->child;
+
+	if (end <= start || start < root->start || !tmp)
+		return 0;
+
+	/* find first overlap */
+	while (tmp && tmp->end < start)
+		tmp = tmp->sibling;
+
+	/* no entries overlap */
+	if (!tmp)  return 0;
+
+	/* found one that starts behind the new one
+	** Don't need to do anything.
+	*/
+	if (tmp->start >= end) return 0;
+
+	if (tmp->start <= start) {
+		/* "front" of new one overlaps */
+		new->start = tmp->end + 1;
+
+		if (tmp->end >= end) {
+			/* AACCKK! totally overlaps! drop this range. */
+			return 1;
+		}
+	} 
+
+	if (tmp->end < end ) {
+		/* "end" of new one overlaps */
+		new->end = tmp->start - 1;
+	}
+
+	printk(KERN_WARNING "LBA: Truncating lmmio_space [%lx/%lx] "
+					"to [%lx,%lx]\n",
+			start, end,
+			new->start, new->end );
+
+	return 0;	/* truncation successful */
+}
+
+#else
+#define lba_claim_dev_resources(dev) do { } while (0)
+#define truncate_pat_collision(r,n)  (0)
+#endif
+
+/*
 ** The algorithm is generic code.
 ** But it needs to access local data structures to get the IRQ base.
 ** Could make this a "pci_fixup_irq(bus, region)" but not sure
@@ -747,6 +807,9 @@
 			lba_dump_res(&ioport_resource, 2);
 			BUG();
 		}
+		/* advertize Host bridge resources to PCI bus */
+		bus->resource[0] = &(ldev->hba.io_space);
+		i = 1;
 
 		if (ldev->hba.elmmio_space.start) {
 			err = request_resource(&iomem_resource,
@@ -760,23 +823,35 @@
 
 				/* lba_dump_res(&iomem_resource, 2); */
 				/* BUG(); */
-			}
+			} else
+				bus->resource[i++] = &(ldev->hba.elmmio_space);
 		}
 
-		err = request_resource(&iomem_resource, &(ldev->hba.lmmio_space));
-		if (err < 0) {
-			/*   FIXME  overlaps with elmmio will fail here.
-			 *   Need to prune (or disable) the distributed range.
-			 *
-			 *   BEWARE: conflicts with this lmmio range may be
-			 *   elmmio range which is pointing down another rope.
-			 */
 
-			printk("FAILED: lba_fixup_bus() request for "
+		/*   Overlaps with elmmio can (and should) fail here.
+		 *   We will prune (or ignore) the distributed range.
+		 *
+		 *   FIXME: SBA code should register all elmmio ranges first.
+		 *      that would take care of elmmio ranges routed
+		 *	to a different rope (already discovered) from
+		 *	getting registered *after* LBA code has already
+		 *	registered it's distributed lmmio range.
+		 */
+		if (truncate_pat_collision(&iomem_resource,
+				       	&(ldev->hba.lmmio_space))) {
+
+			printk(KERN_WARNING "LBA: lmmio_space [%lx/%lx] duplicate!\n",
+					ldev->hba.lmmio_space.start,
+					ldev->hba.lmmio_space.end);
+		} else {
+			err = request_resource(&iomem_resource, &(ldev->hba.lmmio_space));
+			if (err < 0) {
+				printk(KERN_ERR "FAILED: lba_fixup_bus() request for "
 					"lmmio_space [%lx/%lx]\n",
 					ldev->hba.lmmio_space.start,
 					ldev->hba.lmmio_space.end);
-			/* lba_dump_res(&iomem_resource, 2); */
+			} else
+				bus->resource[i++] = &(ldev->hba.lmmio_space);
 		}
 
 #ifdef CONFIG_64BIT
@@ -791,18 +866,10 @@
 				lba_dump_res(&iomem_resource, 2);
 				BUG();
 			}
+			bus->resource[i++] = &(ldev->hba.gmmio_space);
 		}
 #endif
 
-		/* advertize Host bridge resources to PCI bus */
-		bus->resource[0] = &(ldev->hba.io_space);
-		bus->resource[1] = &(ldev->hba.lmmio_space);
-		i=2;
-		if (ldev->hba.elmmio_space.start)
-			bus->resource[i++] = &(ldev->hba.elmmio_space);
-		if (ldev->hba.gmmio_space.start)
-			bus->resource[i++] = &(ldev->hba.gmmio_space);
-			
 	}
 
 	list_for_each(ln, &bus->devices) {