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
- Lines: 599
- Date:
2003-06-13 07:51:31.000000000 -0700
- Orig file:
linux-2.4.20/arch/ppc/kernel/pci.c
- Orig date:
2002-11-28 15:53:11.000000000 -0800
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)