patch-2.4.21 linux-2.4.21/arch/ppc/kernel/pci.c

Next file: linux-2.4.21/arch/ppc/kernel/pci.h
Previous file: linux-2.4.21/arch/ppc/kernel/pci-dma.c
Back to the patch index
Back to the overall index

diff -urN linux-2.4.20/arch/ppc/kernel/pci.c linux-2.4.21/arch/ppc/kernel/pci.c
@@ -1,7 +1,4 @@
 /*
- * BK Id: %F% %I% %G% %U% %#%
- */
-/*
  * Common pmac/prep/chrp pci routines. -- Cort
  */
 
@@ -39,8 +36,13 @@
 
 void pcibios_make_OF_bus_map(void);
 
+static int pci_relocate_bridge_resource(struct pci_bus *bus, int i);
+static int probe_resource(struct pci_bus *parent, struct resource *pr,
+			  struct resource *res, struct resource **conflict);
+static void update_bridge_base(struct pci_bus *bus, int i);
 static void pcibios_fixup_resources(struct pci_dev* dev);
 static void fixup_broken_pcnet32(struct pci_dev* dev);
+static int reparent_resources(struct resource *parent, struct resource *res);
 static void fixup_rev1_53c810(struct pci_dev* dev);
 static void fixup_cpc710_pci64(struct pci_dev* dev);
 #ifdef CONFIG_ALL_PPC
@@ -107,7 +109,7 @@
 
 void
 pcibios_update_resource(struct pci_dev *dev, struct resource *root,
-			     struct resource *res, int resource)
+			struct resource *res, int resource)
 {
 	u32 new, check;
 	int reg;
@@ -115,6 +117,7 @@
 	unsigned long io_offset;
 	
 	new = res->start;
+	res->flags &= ~IORESOURCE_UNSET;
 	if (hose && res->flags & IORESOURCE_IO) {
 		io_offset = (unsigned long)hose->io_base_virt - isa_io_base;
 		new -= io_offset;
@@ -139,6 +142,9 @@
 		       "%s/%d (%08x != %08x)\n", dev->slot_name, resource,
 		       new, check);
 	}
+	printk(KERN_INFO "PCI: moved device %s resource %d (%lx) to %x\n",
+	       dev->slot_name, resource, res->flags,
+	       new & ~PCI_REGION_FLAG_MASK);
 }
 
 static void
@@ -154,13 +160,14 @@
 	}
 	for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) {
 		struct resource *res = dev->resource + i;
-		if (!res->start || !res->flags)
+		if (!res->flags)
 			continue;
-		if (res->end == 0xffffffff) {
+		if (!res->start || res->end == 0xffffffff) {
 			DBG("PCI:%s Resource %d [%08lx-%08lx] is unassigned\n",
 			    dev->slot_name, i, res->start, res->end);
 			res->end -= res->start;
 			res->start = 0;
+			res->flags |= IORESOURCE_UNSET;
 			continue;
 		}
 		offset = 0;
@@ -304,7 +311,8 @@
 	for (ln = bus_list->next; ln != bus_list; ln=ln->next) {
 		bus = pci_bus_b(ln);
 		for (i = 0; i < 4; ++i) {
-			if ((res = bus->resource[i]) == NULL || !res->flags)
+			if ((res = bus->resource[i]) == NULL || !res->flags
+			    || res->start > res->end)
 				continue;
 			if (bus->parent == NULL)
 				pr = (res->flags & IORESOURCE_IO)?
@@ -319,18 +327,238 @@
 					continue;
 				}
 			}
-			
-			if (pr && request_resource(pr, res) == 0)
-				continue;
+
+			DBG("PCI: bridge rsrc %lx..%lx (%lx), parent %p\n",
+			    res->start, res->end, res->flags, pr);
+			if (pr) {
+				if (request_resource(pr, res) == 0)
+					continue;
+				/*
+				 * Must be a conflict with an existing entry.
+				 * Move that entry (or entries) under the
+				 * bridge resource and try again.
+				 */
+				if (reparent_resources(pr, res) == 0)
+					continue;
+			}
 			printk(KERN_ERR "PCI: Cannot allocate resource region "
 			       "%d of PCI bridge %d\n", i, bus->number);
-			DBG("PCI: resource is %lx..%lx (%lx), parent %p\n",
-			    res->start, res->end, res->flags, pr);
+			if (pci_relocate_bridge_resource(bus, i))
+				bus->resource[i] = NULL;
 		}
 		pcibios_allocate_bus_resources(&bus->children);
 	}
 }
 
+/*
+ * Reparent resource children of pr that conflict with res
+ * under res, and make res replace those children.
+ */
+static int __init
+reparent_resources(struct resource *parent, struct resource *res)
+{
+	struct resource *p, **pp;
+	struct resource **firstpp = NULL;
+
+	for (pp = &parent->child; (p = *pp) != NULL; pp = &p->sibling) {
+		if (p->end < res->start)
+			continue;
+		if (res->end < p->start)
+			break;
+		if (p->start < res->start || p->end > res->end)
+			return -1;	/* not completely contained */
+		if (firstpp == NULL)
+			firstpp = pp;
+	}
+	if (firstpp == NULL)
+		return -1;	/* didn't find any conflicting entries? */
+	res->parent = parent;
+	res->child = *firstpp;
+	res->sibling = *pp;
+	*firstpp = res;
+	*pp = NULL;
+	for (p = res->child; p != NULL; p = p->sibling) {
+		p->parent = res;
+		DBG(KERN_INFO "PCI: reparented %s [%lx..%lx] under %s\n",
+		    p->name, p->start, p->end, res->name);
+	}
+	return 0;
+}
+
+/*
+ * A bridge has been allocated a range which is outside the range
+ * of its parent bridge, so it needs to be moved.
+ */
+static int __init
+pci_relocate_bridge_resource(struct pci_bus *bus, int i)
+{
+	struct resource *res, *pr, *conflict;
+	unsigned long try, size;
+	int j;
+	struct pci_bus *parent = bus->parent;
+
+	if (parent == NULL) {
+		/* shouldn't ever happen */
+		printk(KERN_ERR "PCI: can't move host bridge resource\n");
+		return -1;
+	}
+	res = bus->resource[i];
+	pr = NULL;
+	for (j = 0; j < 4; j++) {
+		struct resource *r = parent->resource[j];
+		if (!r)
+			continue;
+		if ((res->flags ^ r->flags) & (IORESOURCE_IO | IORESOURCE_MEM))
+			continue;
+		if (!((res->flags ^ r->flags) & IORESOURCE_PREFETCH)) {
+			pr = r;
+			break;
+		}
+		if (res->flags & IORESOURCE_PREFETCH)
+			pr = r;
+	}
+	if (pr == NULL)
+		return -1;
+	size = res->end - res->start;
+	if (pr->start > pr->end || size > pr->end - pr->start)
+		return -1;
+	try = pr->end + 1;
+	for (;;) {
+		try &= ~0xfffUL;
+		if (res->flags & IORESOURCE_MEM)
+			try &= ~0xfffffUL;
+		res->start = try - size - 1;
+		res->end = try - 1;
+		if (try <= size || res->start < pr->start)
+			return -1;
+		if (probe_resource(bus->parent, pr, res, &conflict) == 0)
+			break;
+		if (conflict->start <= pr->start + size)
+			return -1;
+		try = conflict->start;
+	}
+	if (request_resource(pr, res)) {
+		DBG(KERN_ERR "PCI: huh? couldn't move to %lx..%lx\n",
+		    res->start, res->end);
+		return -1;		/* "can't happen" */
+	}
+	update_bridge_base(bus, i);
+	printk(KERN_INFO "PCI: bridge %d resource %d moved to %lx..%lx\n",
+	       bus->number, i, res->start, res->end);
+	return 0;
+}
+
+static int __init
+probe_resource(struct pci_bus *parent, struct resource *pr,
+	       struct resource *res, struct resource **conflict)
+{
+	struct pci_bus *bus;
+	struct pci_dev *dev;
+	struct resource *r;
+	struct list_head *ln;
+	int i;
+
+	for (r = pr->child; r != NULL; r = r->sibling) {
+		if (r->end >= res->start && res->end >= r->start) {
+			*conflict = r;
+			return 1;
+		}
+	}
+	for (ln = parent->children.next; ln != &parent->children;
+	     ln = ln->next) {
+		bus = pci_bus_b(ln);
+		for (i = 0; i < 4; ++i) {
+			if ((r = bus->resource[i]) == NULL)
+				continue;
+			if (!r->flags || r->start > r->end || r == res)
+				continue;
+			if (pci_find_parent_resource(bus->self, r) != pr)
+				continue;
+			if (r->end >= res->start && res->end >= r->start) {
+				*conflict = r;
+				return 1;
+			}
+		}
+	}
+	for (ln = parent->devices.next; ln != &parent->devices; ln=ln->next) {
+		dev = pci_dev_b(ln);
+		for (i = 0; i < 6; ++i) {
+			r = &dev->resource[i];
+			if (!r->flags || (r->flags & IORESOURCE_UNSET))
+				continue;
+			if (pci_find_parent_resource(bus->self, r) != pr)
+				continue;
+			if (r->end >= res->start && res->end >= r->start) {
+				*conflict = r;
+				return 1;
+			}
+		}
+	}
+	return 0;
+}
+
+static void __init
+update_bridge_base(struct pci_bus *bus, int i)
+{
+	struct resource *res = bus->resource[i];
+	u8 io_base_lo, io_limit_lo;
+	u16 mem_base, mem_limit;
+	u16 cmd;
+	unsigned long start, end, off;
+	struct pci_dev *dev = bus->self;
+	struct pci_controller *hose = dev->sysdata;
+
+	if (!hose) {
+		printk("update_bridge_base: no hose?\n");
+		return;
+	}
+	pci_read_config_word(dev, PCI_COMMAND, &cmd);
+	pci_write_config_word(dev, PCI_COMMAND,
+			      cmd & ~(PCI_COMMAND_IO | PCI_COMMAND_MEMORY));
+	if (res->flags & IORESOURCE_IO) {
+		u16 bu;
+		off = (unsigned long) hose->io_base_virt - isa_io_base;
+		start = res->start - off;
+		end = res->end - off;
+		pci_read_config_byte(dev, PCI_IO_BASE, &io_base_lo);
+		pci_read_config_byte(dev, PCI_IO_LIMIT, &io_limit_lo);
+		pci_read_config_word(dev, PCI_IO_BASE_UPPER16, &bu);
+		io_limit_lo &= PCI_IO_RANGE_TYPE_MASK;
+		if (io_base_lo == PCI_IO_RANGE_TYPE_16 && end > 0xffff) {
+			printk(KERN_ERR "bridge only supports 16-bit I/O!\n");
+			goto out;
+		}
+		io_base_lo |= (start >> 8) & PCI_IO_RANGE_MASK;
+		io_limit_lo |= (end >> 8) & PCI_IO_RANGE_MASK;
+		pci_write_config_word(dev, PCI_IO_BASE_UPPER16, start >> 16);
+		pci_write_config_word(dev, PCI_IO_LIMIT_UPPER16, end >> 16);
+		pci_write_config_byte(dev, PCI_IO_BASE, io_base_lo);
+		pci_write_config_byte(dev, PCI_IO_LIMIT, io_limit_lo);
+
+	} else if ((res->flags & (IORESOURCE_MEM | IORESOURCE_PREFETCH))
+		   == IORESOURCE_MEM) {
+		off = hose->pci_mem_offset;
+		mem_base = ((res->start - off) >> 16) & PCI_MEMORY_RANGE_MASK;
+		mem_limit = ((res->end - off) >> 16) & PCI_MEMORY_RANGE_MASK;
+		pci_write_config_word(dev, PCI_MEMORY_BASE, mem_base);
+		pci_write_config_word(dev, PCI_MEMORY_LIMIT, mem_limit);
+
+	} else if ((res->flags & (IORESOURCE_MEM | IORESOURCE_PREFETCH))
+		   == (IORESOURCE_MEM | IORESOURCE_PREFETCH)) {
+		off = hose->pci_mem_offset;
+		mem_base = ((res->start - off) >> 16) & PCI_PREF_RANGE_MASK;
+		mem_limit = ((res->end - off) >> 16) & PCI_PREF_RANGE_MASK;
+		pci_write_config_word(dev, PCI_PREF_MEMORY_BASE, mem_base);
+		pci_write_config_word(dev, PCI_PREF_MEMORY_LIMIT, mem_limit);
+
+	} else {
+		DBG(KERN_ERR "PCI: ugh, bridge %s res %d has flags=%lx\n",
+		    dev->slot_name, i, res->flags);
+	}
+ out:
+	pci_write_config_word(dev, PCI_COMMAND, cmd);
+}
+
 static inline void alloc_resource(struct pci_dev *dev, int idx)
 {
 	struct resource *pr, *r = &dev->resource[idx];
@@ -345,6 +573,7 @@
 			DBG("PCI:  parent is %p: %08lx-%08lx (f=%lx)\n",
 			    pr, pr->start, pr->end, pr->flags);
 		/* We'll assign a new address later */
+		r->flags |= IORESOURCE_UNSET;
 		r->end -= r->start;
 		r->start = 0;
 	}
@@ -364,8 +593,8 @@
 			r = &dev->resource[idx];
 			if (r->parent)		/* Already allocated */
 				continue;
-			if (!r->start)		/* Not assigned at all */
-				continue;
+			if (!r->flags || (r->flags & IORESOURCE_UNSET))
+				continue;	/* Not assigned at all */
 			if (r->flags & IORESOURCE_IO)
 				disabled = !(command & PCI_COMMAND_IO);
 			else
@@ -411,7 +640,7 @@
 			 * or because we have decided the old address was
 			 * unusable for some reason.
 			 */
-			if (!r->start && r->end &&
+			if ((r->flags & IORESOURCE_UNSET) && r->end &&
 			    (!ppc_md.pcibios_enable_device_hook ||
 			     !ppc_md.pcibios_enable_device_hook(dev, 1)))
 				pci_assign_resource(dev, idx);
@@ -437,9 +666,9 @@
 
 	pci_read_config_word(dev, PCI_COMMAND, &cmd);
 	old_cmd = cmd;
-	for(idx=0; idx<6; idx++) {
+	for (idx=0; idx<6; idx++) {
 		r = &dev->resource[idx];
-		if (!r->start && r->end) {
+		if (r->flags & IORESOURCE_UNSET) {
 			printk(KERN_ERR "PCI: Device %s not available because of resource collisions\n", dev->slot_name);
 			return -EINVAL;
 		}
@@ -490,7 +719,7 @@
 	bus_range = (int *) get_property(node, "bus-range", &len);
 	if (bus_range == NULL || len < 2 * sizeof(int)) {
 		printk(KERN_WARNING "Can't get bus-range for %s\n",
-			       node->full_name);
+		       node->full_name);
 		return;
 	}
 	pci_to_OF_bus_map[pci_bus] = bus_range[0];
@@ -717,8 +946,9 @@
 	int rlen = 0;
 	int memno = 0;
 	struct resource *res;
-	int np, na = prom_n_addr_cells(dev);
-	np = na + 5;
+	int na = prom_n_addr_cells(dev);
+	int np = na + 5;
+	unsigned int size;
 
 	/* First we try to merge ranges to fix a problem with some pmacs
 	 * that can have more than 3 ranges, fortunately using contiguous
@@ -754,12 +984,16 @@
 	ranges = (unsigned int *) get_property(dev, "ranges", &rlen);
 	while ((rlen -= np * sizeof(unsigned int)) >= 0) {
 		res = NULL;
+		size = ranges[na+4];
 		switch (ranges[0] >> 24) {
 		case 1:		/* I/O space */
 			if (ranges[2] != 0)
 				break;
 			hose->io_base_phys = ranges[na+2];
-			hose->io_base_virt = ioremap(ranges[na+2], ranges[na+4]);
+			/* limit I/O to 16MB */
+			if (size > 0x01000000)
+				size = 0x01000000;
+			hose->io_base_virt = ioremap(ranges[na+2], size);
 			if (primary)
 				isa_io_base = (unsigned long) hose->io_base_virt;
 			res = &hose->io_resource;
@@ -788,7 +1022,7 @@
 		}
 		if (res != NULL) {
 			res->name = dev->full_name;
-			res->end = res->start + ranges[na+4] - 1;
+			res->end = res->start + size - 1;
 			res->parent = NULL;
 			res->sibling = NULL;
 			res->child = NULL;
@@ -894,7 +1128,8 @@
 		struct pci_dev *dev = pci_dev_b(ln);
 		u16 class = dev->class >> 8;
 
-		if (class == PCI_CLASS_DISPLAY_VGA || class == PCI_CLASS_NOT_DEFINED_VGA)
+		if (class == PCI_CLASS_DISPLAY_VGA ||
+		    class == PCI_CLASS_NOT_DEFINED_VGA)
 			*found_vga = 1;
 		if (class >> 8 == PCI_BASE_CLASS_BRIDGE && dev->subordinate)
 			rc |= check_for_io_childs(dev->subordinate, res, found_vga);
@@ -905,7 +1140,8 @@
 			struct resource *r;
 			unsigned long r_size;
 
-			if (dev->class >> 8 == PCI_CLASS_BRIDGE_PCI && i >= PCI_BRIDGE_RESOURCES)
+			if (dev->class >> 8 == PCI_CLASS_BRIDGE_PCI
+			    && i >= PCI_BRIDGE_RESOURCES)
 				continue;
 			r = &dev->resource[i];
 			r_size = r->end - r->start;
@@ -931,30 +1167,45 @@
 do_fixup_p2p_level(struct pci_bus *bus)
 {
 	struct list_head *ln;
-	int i;
+	int i, parent_io;
 	int has_vga = 0;
+
+	for (parent_io=0; parent_io<4; parent_io++)
+		if (bus->resource[parent_io]->flags & IORESOURCE_IO)
+			break;
+	if (parent_io >= 4)
+		return;
 	
 	for (ln=bus->children.next; ln != &bus->children; ln=ln->next) {
 		struct pci_bus *b = pci_bus_b(ln);
 		struct pci_dev *d = b->self;
 		struct pci_controller* hose = (struct pci_controller *)d->sysdata;
 		struct resource *res = b->resource[0];
+		struct resource tmp_res;
 		unsigned long max;
 		int found_vga = 0;
 
-		res->end = 0;
-		res->start = 0x1000;
+		memset(&tmp_res, 0, sizeof(tmp_res));
+		tmp_res.start = bus->resource[parent_io]->start;
+
+		/* We don't let low addresses go through that closed P2P bridge, well,
+		 * that may not be necessary but I feel safer that way
+		 */
+		if (tmp_res.start == 0)
+			tmp_res.start = 0x1000;
 		
-		if (!list_empty(&b->devices) && res && res->flags == 0 && res != bus->resource[0] &&
+		if (!list_empty(&b->devices) && res && res->flags == 0 &&
+		    res != bus->resource[parent_io] &&
 		    (d->class >> 8) == PCI_CLASS_BRIDGE_PCI &&
-		    check_for_io_childs(b, res, &found_vga)) {
+		    check_for_io_childs(b, &tmp_res, &found_vga)) {
 			u8 io_base_lo;
 
 			printk(KERN_INFO "Fixing up IO bus %s\n", b->name);
 
 			if (found_vga) {
 				if (has_vga) {
-					printk(KERN_WARNING "Skipping VGA, already active on bus segment\n");
+					printk(KERN_WARNING "Skipping VGA, already active"
+					    " on bus segment\n");
 					found_vga = 0;
 				} else
 					has_vga = 1;
@@ -962,10 +1213,13 @@
 			pci_read_config_byte(d, PCI_IO_BASE, &io_base_lo);
 
 			if ((io_base_lo & PCI_IO_RANGE_TYPE_MASK) == PCI_IO_RANGE_TYPE_32)
-				max = ((unsigned long) hose->io_base_virt - isa_io_base) + 0xffffffff;
+				max = ((unsigned long) hose->io_base_virt
+					- isa_io_base) + 0xffffffff;
 			else
-				max = ((unsigned long) hose->io_base_virt - isa_io_base) + 0xffff;
-			
+				max = ((unsigned long) hose->io_base_virt
+					- isa_io_base) + 0xffff;
+
+			*res = tmp_res;
 			res->flags = IORESOURCE_IO;
 			res->name = b->name;
 			
@@ -976,10 +1230,12 @@
 					continue;
 				if ((r->flags & IORESOURCE_IO) == 0)
 					continue;
-				DBG("Trying to allocate from %08lx, size %08lx from parent res %d: %08lx -> %08lx\n",
+				DBG("Trying to allocate from %08lx, size %08lx from parent"
+				    " res %d: %08lx -> %08lx\n",
 					res->start, res->end, i, r->start, r->end);
 				
-				if (allocate_resource(r, res, res->end + 1, res->start, max, res->end + 1, NULL, NULL) < 0) {
+				if (allocate_resource(r, res, res->end + 1, res->start, max,
+				    res->end + 1, NULL, NULL) < 0) {
 					DBG("Failed !\n");
 					continue;
 				}
@@ -1191,7 +1447,7 @@
 			continue;
 			
 		r = &dev->resource[idx];
-		if (!r->start && r->end) {
+		if (r->flags & IORESOURCE_UNSET) {
 			printk(KERN_ERR "PCI: Device %s not available because of resource collisions\n", dev->slot_name);
 			return -EINVAL;
 		}
@@ -1298,51 +1554,41 @@
 {
 	struct pci_controller *hose = (struct pci_controller *) dev->sysdata;
 	unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
-	unsigned long io_offset = 0;
-	int i, res_bit;
+	unsigned long size = vma->vm_end - vma->vm_start;
+	unsigned long base;
+	struct resource *res;
+	int i;
+	int ret = -EINVAL;
 
 	if (hose == 0)
 		return -EINVAL;		/* should never happen */
+	if (offset + size <= offset)
+		return -EINVAL;
 
-	/* If memory, add on the PCI bridge address offset */
 	if (mmap_state == pci_mmap_mem) {
+		/* PCI memory space */
+		base = hose->pci_mem_offset;
+		for (i = 0; i < 3; ++i) {
+			res = &hose->mem_resources[i];
+			if (offset >= res->start - base
+			    && offset + size - 1 <= res->end - base) {
+				ret = 0;
+				break;
+			}
+		}
 		offset += hose->pci_mem_offset;
-		res_bit = IORESOURCE_MEM;
 	} else {
-		io_offset = (unsigned long)hose->io_base_virt - isa_io_base;
-		offset += io_offset;
-		res_bit = IORESOURCE_IO;
+		/* PCI I/O space */
+		base = (unsigned long)hose->io_base_virt - isa_io_base;
+		res = &hose->io_resource;
+		if (offset >= res->start - base
+		    && offset + size - 1 <= res->end - base)
+			ret = 0;
+		offset += hose->io_base_phys;
 	}
 
-	/*
-	 * Check that the offset requested corresponds to one of the
-	 * resources of the device.
-	 */
-	for (i = 0; i <= PCI_ROM_RESOURCE; i++) {
-		struct resource *rp = &dev->resource[i];
-		int flags = rp->flags;
-
-		/* treat ROM as memory (should be already) */
-		if (i == PCI_ROM_RESOURCE)
-			flags |= IORESOURCE_MEM;
-
-		/* Active and same type? */
-		if ((flags & res_bit) == 0)
-			continue;
-
-		/* In the range of this resource? */
-		if (offset < (rp->start & PAGE_MASK) || offset > rp->end)
-			continue;
-
-		/* found it! construct the final physical address */
-		if (mmap_state == pci_mmap_io)
-			offset += hose->io_base_phys - io_offset;
-
-		vma->vm_pgoff = offset >> PAGE_SHIFT;
-		return 0;
-	}
-
-	return -EINVAL;
+	vma->vm_pgoff = offset >> PAGE_SHIFT;
+	return ret;
 }
 
 /*

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)