diff options
Diffstat (limited to 'drivers/pci/setup-bus.c')
-rw-r--r-- | drivers/pci/setup-bus.c | 660 |
1 files changed, 459 insertions, 201 deletions
diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index 86b69f85..8fa2d4b 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -25,10 +25,13 @@ #include <linux/ioport.h> #include <linux/cache.h> #include <linux/slab.h> +#include <asm-generic/pci-bridge.h> #include "pci.h" -struct resource_list_x { - struct resource_list_x *next; +unsigned int pci_flags; + +struct pci_dev_resource { + struct list_head list; struct resource *res; struct pci_dev *dev; resource_size_t start; @@ -38,21 +41,14 @@ struct resource_list_x { unsigned long flags; }; -#define free_list(type, head) do { \ - struct type *list, *tmp; \ - for (list = (head)->next; list;) { \ - tmp = list; \ - list = list->next; \ - kfree(tmp); \ - } \ - (head)->next = NULL; \ -} while (0) - -int pci_realloc_enable = 0; -#define pci_realloc_enabled() pci_realloc_enable -void pci_realloc(void) +static void free_list(struct list_head *head) { - pci_realloc_enable = 1; + struct pci_dev_resource *dev_res, *tmp; + + list_for_each_entry_safe(dev_res, tmp, head, list) { + list_del(&dev_res->list); + kfree(dev_res); + } } /** @@ -64,21 +60,18 @@ void pci_realloc(void) * @add_size: additional size to be optionally added * to the resource */ -static void add_to_list(struct resource_list_x *head, +static int add_to_list(struct list_head *head, struct pci_dev *dev, struct resource *res, resource_size_t add_size, resource_size_t min_align) { - struct resource_list_x *list = head; - struct resource_list_x *ln = list->next; - struct resource_list_x *tmp; + struct pci_dev_resource *tmp; - tmp = kmalloc(sizeof(*tmp), GFP_KERNEL); + tmp = kzalloc(sizeof(*tmp), GFP_KERNEL); if (!tmp) { pr_warning("add_to_list: kmalloc() failed!\n"); - return; + return -ENOMEM; } - tmp->next = ln; tmp->res = res; tmp->dev = dev; tmp->start = res->start; @@ -86,19 +79,100 @@ static void add_to_list(struct resource_list_x *head, tmp->flags = res->flags; tmp->add_size = add_size; tmp->min_align = min_align; - list->next = tmp; + + list_add(&tmp->list, head); + + return 0; } -static void add_to_failed_list(struct resource_list_x *head, - struct pci_dev *dev, struct resource *res) +static void remove_from_list(struct list_head *head, + struct resource *res) { - add_to_list(head, dev, res, - 0 /* dont care */, - 0 /* dont care */); + struct pci_dev_resource *dev_res, *tmp; + + list_for_each_entry_safe(dev_res, tmp, head, list) { + if (dev_res->res == res) { + list_del(&dev_res->list); + kfree(dev_res); + break; + } + } +} + +static resource_size_t get_res_add_size(struct list_head *head, + struct resource *res) +{ + struct pci_dev_resource *dev_res; + + list_for_each_entry(dev_res, head, list) { + if (dev_res->res == res) { + int idx = res - &dev_res->dev->resource[0]; + + dev_printk(KERN_DEBUG, &dev_res->dev->dev, + "res[%d]=%pR get_res_add_size add_size %llx\n", + idx, dev_res->res, + (unsigned long long)dev_res->add_size); + + return dev_res->add_size; + } + } + + return 0; +} + +/* Sort resources by alignment */ +static void pdev_sort_resources(struct pci_dev *dev, struct list_head *head) +{ + int i; + + for (i = 0; i < PCI_NUM_RESOURCES; i++) { + struct resource *r; + struct pci_dev_resource *dev_res, *tmp; + resource_size_t r_align; + struct list_head *n; + + r = &dev->resource[i]; + + if (r->flags & IORESOURCE_PCI_FIXED) + continue; + + if (!(r->flags) || r->parent) + continue; + + r_align = pci_resource_alignment(dev, r); + if (!r_align) { + dev_warn(&dev->dev, "BAR %d: %pR has bogus alignment\n", + i, r); + continue; + } + + tmp = kzalloc(sizeof(*tmp), GFP_KERNEL); + if (!tmp) + panic("pdev_sort_resources(): " + "kmalloc() failed!\n"); + tmp->res = r; + tmp->dev = dev; + + /* fallback is smallest one or list is empty*/ + n = head; + list_for_each_entry(dev_res, head, list) { + resource_size_t align; + + align = pci_resource_alignment(dev_res->dev, + dev_res->res); + + if (r_align > align) { + n = &dev_res->list; + break; + } + } + /* Insert it just before n*/ + list_add_tail(&tmp->list, n); + } } static void __dev_sort_resources(struct pci_dev *dev, - struct resource_list *head) + struct list_head *head) { u16 class = dev->class >> 8; @@ -136,49 +210,54 @@ static inline void reset_resource(struct resource *res) * additional resources for the element, provided the element * is in the head list. */ -static void reassign_resources_sorted(struct resource_list_x *realloc_head, - struct resource_list *head) +static void reassign_resources_sorted(struct list_head *realloc_head, + struct list_head *head) { struct resource *res; - struct resource_list_x *list, *tmp, *prev; - struct resource_list *hlist; + struct pci_dev_resource *add_res, *tmp; + struct pci_dev_resource *dev_res; resource_size_t add_size; int idx; - prev = realloc_head; - for (list = realloc_head->next; list;) { - res = list->res; + list_for_each_entry_safe(add_res, tmp, realloc_head, list) { + bool found_match = false; + + res = add_res->res; /* skip resource that has been reset */ if (!res->flags) goto out; /* skip this resource if not found in head list */ - for (hlist = head->next; hlist && hlist->res != res; - hlist = hlist->next); - if (!hlist) { /* just skip */ - prev = list; - list = list->next; - continue; + list_for_each_entry(dev_res, head, list) { + if (dev_res->res == res) { + found_match = true; + break; + } } + if (!found_match)/* just skip */ + continue; - idx = res - &list->dev->resource[0]; - add_size=list->add_size; + idx = res - &add_res->dev->resource[0]; + add_size = add_res->add_size; if (!resource_size(res)) { - res->start = list->start; + res->start = add_res->start; res->end = res->start + add_size - 1; - if(pci_assign_resource(list->dev, idx)) + if (pci_assign_resource(add_res->dev, idx)) reset_resource(res); } else { - resource_size_t align = list->min_align; - res->flags |= list->flags & (IORESOURCE_STARTALIGN|IORESOURCE_SIZEALIGN); - if (pci_reassign_resource(list->dev, idx, add_size, align)) - dev_printk(KERN_DEBUG, &list->dev->dev, "failed to add optional resources res=%pR\n", - res); + resource_size_t align = add_res->min_align; + res->flags |= add_res->flags & + (IORESOURCE_STARTALIGN|IORESOURCE_SIZEALIGN); + if (pci_reassign_resource(add_res->dev, idx, + add_size, align)) + dev_printk(KERN_DEBUG, &add_res->dev->dev, + "failed to add %llx res[%d]=%pR\n", + (unsigned long long)add_size, + idx, res); } out: - tmp = list; - prev->next = list = list->next; - kfree(tmp); + list_del(&add_res->list); + kfree(add_res); } } @@ -192,35 +271,99 @@ out: * Satisfy resource requests of each element in the list. Add * requests that could not satisfied to the failed_list. */ -static void assign_requested_resources_sorted(struct resource_list *head, - struct resource_list_x *fail_head) +static void assign_requested_resources_sorted(struct list_head *head, + struct list_head *fail_head) { struct resource *res; - struct resource_list *list; + struct pci_dev_resource *dev_res; int idx; - for (list = head->next; list; list = list->next) { - res = list->res; - idx = res - &list->dev->resource[0]; - if (resource_size(res) && pci_assign_resource(list->dev, idx)) { - if (fail_head && !pci_is_root_bus(list->dev->bus)) { + list_for_each_entry(dev_res, head, list) { + res = dev_res->res; + idx = res - &dev_res->dev->resource[0]; + if (resource_size(res) && + pci_assign_resource(dev_res->dev, idx)) { + if (fail_head && !pci_is_root_bus(dev_res->dev->bus)) { /* * if the failed res is for ROM BAR, and it will * be enabled later, don't add it to the list */ if (!((idx == PCI_ROM_RESOURCE) && (!(res->flags & IORESOURCE_ROM_ENABLE)))) - add_to_failed_list(fail_head, list->dev, res); + add_to_list(fail_head, + dev_res->dev, res, + 0 /* dont care */, + 0 /* dont care */); } reset_resource(res); } } } -static void __assign_resources_sorted(struct resource_list *head, - struct resource_list_x *realloc_head, - struct resource_list_x *fail_head) +static void __assign_resources_sorted(struct list_head *head, + struct list_head *realloc_head, + struct list_head *fail_head) { + /* + * Should not assign requested resources at first. + * they could be adjacent, so later reassign can not reallocate + * them one by one in parent resource window. + * Try to assign requested + add_size at begining + * if could do that, could get out early. + * if could not do that, we still try to assign requested at first, + * then try to reassign add_size for some resources. + */ + LIST_HEAD(save_head); + LIST_HEAD(local_fail_head); + struct pci_dev_resource *save_res; + struct pci_dev_resource *dev_res; + + /* Check if optional add_size is there */ + if (!realloc_head || list_empty(realloc_head)) + goto requested_and_reassign; + + /* Save original start, end, flags etc at first */ + list_for_each_entry(dev_res, head, list) { + if (add_to_list(&save_head, dev_res->dev, dev_res->res, 0, 0)) { + free_list(&save_head); + goto requested_and_reassign; + } + } + + /* Update res in head list with add_size in realloc_head list */ + list_for_each_entry(dev_res, head, list) + dev_res->res->end += get_res_add_size(realloc_head, + dev_res->res); + + /* Try updated head list with add_size added */ + assign_requested_resources_sorted(head, &local_fail_head); + + /* all assigned with add_size ? */ + if (list_empty(&local_fail_head)) { + /* Remove head list from realloc_head list */ + list_for_each_entry(dev_res, head, list) + remove_from_list(realloc_head, dev_res->res); + free_list(&save_head); + free_list(head); + return; + } + + free_list(&local_fail_head); + /* Release assigned resource */ + list_for_each_entry(dev_res, head, list) + if (dev_res->res->parent) + release_resource(dev_res->res); + /* Restore start/end/flags from saved list */ + list_for_each_entry(save_res, &save_head, list) { + struct resource *res = save_res->res; + + res->start = save_res->start; + res->end = save_res->end; + res->flags = save_res->flags; + } + free_list(&save_head); + +requested_and_reassign: /* Satisfy the must-have resource requests */ assign_requested_resources_sorted(head, fail_head); @@ -228,28 +371,27 @@ static void __assign_resources_sorted(struct resource_list *head, requests */ if (realloc_head) reassign_resources_sorted(realloc_head, head); - free_list(resource_list, head); + free_list(head); } static void pdev_assign_resources_sorted(struct pci_dev *dev, - struct resource_list_x *fail_head) + struct list_head *add_head, + struct list_head *fail_head) { - struct resource_list head; + LIST_HEAD(head); - head.next = NULL; __dev_sort_resources(dev, &head); - __assign_resources_sorted(&head, NULL, fail_head); + __assign_resources_sorted(&head, add_head, fail_head); } static void pbus_assign_resources_sorted(const struct pci_bus *bus, - struct resource_list_x *realloc_head, - struct resource_list_x *fail_head) + struct list_head *realloc_head, + struct list_head *fail_head) { struct pci_dev *dev; - struct resource_list head; + LIST_HEAD(head); - head.next = NULL; list_for_each_entry(dev, &bus->devices, bus_list) __dev_sort_resources(dev, &head); @@ -548,20 +690,6 @@ static resource_size_t calculate_memsize(resource_size_t size, return size; } -static resource_size_t get_res_add_size(struct resource_list_x *realloc_head, - struct resource *res) -{ - struct resource_list_x *list; - - /* check if it is in realloc_head list */ - for (list = realloc_head->next; list && list->res != res; - list = list->next); - if (list) - return list->add_size; - - return 0; -} - /** * pbus_size_io() - size the io window of a given bus * @@ -576,7 +704,7 @@ static resource_size_t get_res_add_size(struct resource_list_x *realloc_head, * We must be careful with the ISA aliasing though. */ static void pbus_size_io(struct pci_bus *bus, resource_size_t min_size, - resource_size_t add_size, struct resource_list_x *realloc_head) + resource_size_t add_size, struct list_head *realloc_head) { struct pci_dev *dev; struct resource *b_res = find_free_bus_resource(bus, IORESOURCE_IO); @@ -612,7 +740,7 @@ static void pbus_size_io(struct pci_bus *bus, resource_size_t min_size, if (children_add_size > add_size) add_size = children_add_size; size1 = (!realloc_head || (realloc_head && !add_size)) ? size0 : - calculate_iosize(size, min_size+add_size, size1, + calculate_iosize(size, min_size, add_size + size1, resource_size(b_res), 4096); if (!size0 && !size1) { if (b_res->start || b_res->end) @@ -626,8 +754,12 @@ static void pbus_size_io(struct pci_bus *bus, resource_size_t min_size, b_res->start = 4096; b_res->end = b_res->start + size0 - 1; b_res->flags |= IORESOURCE_STARTALIGN; - if (size1 > size0 && realloc_head) + if (size1 > size0 && realloc_head) { add_to_list(realloc_head, bus->self, b_res, size1-size0, 4096); + dev_printk(KERN_DEBUG, &bus->self->dev, "bridge window " + "%pR to [bus %02x-%02x] add_size %lx\n", b_res, + bus->secondary, bus->subordinate, size1-size0); + } } /** @@ -644,7 +776,7 @@ static void pbus_size_io(struct pci_bus *bus, resource_size_t min_size, static int pbus_size_mem(struct pci_bus *bus, unsigned long mask, unsigned long type, resource_size_t min_size, resource_size_t add_size, - struct resource_list_x *realloc_head) + struct list_head *realloc_head) { struct pci_dev *dev; resource_size_t min_align, align, size, size0, size1; @@ -726,7 +858,7 @@ static int pbus_size_mem(struct pci_bus *bus, unsigned long mask, if (children_add_size > add_size) add_size = children_add_size; size1 = (!realloc_head || (realloc_head && !add_size)) ? size0 : - calculate_memsize(size, min_size+add_size, 0, + calculate_memsize(size, min_size, add_size, resource_size(b_res), min_align); if (!size0 && !size1) { if (b_res->start || b_res->end) @@ -739,8 +871,12 @@ static int pbus_size_mem(struct pci_bus *bus, unsigned long mask, b_res->start = min_align; b_res->end = size0 + min_align - 1; b_res->flags |= IORESOURCE_STARTALIGN | mem64_mask; - if (size1 > size0 && realloc_head) + if (size1 > size0 && realloc_head) { add_to_list(realloc_head, bus->self, b_res, size1-size0, min_align); + dev_printk(KERN_DEBUG, &bus->self->dev, "bridge window " + "%pR to [bus %02x-%02x] add_size %llx\n", b_res, + bus->secondary, bus->subordinate, (unsigned long long)size1-size0); + } return 1; } @@ -754,25 +890,48 @@ unsigned long pci_cardbus_resource_alignment(struct resource *res) } static void pci_bus_size_cardbus(struct pci_bus *bus, - struct resource_list_x *realloc_head) + struct list_head *realloc_head) { struct pci_dev *bridge = bus->self; struct resource *b_res = &bridge->resource[PCI_BRIDGE_RESOURCES]; + resource_size_t b_res_3_size = pci_cardbus_mem_size * 2; u16 ctrl; + if (b_res[0].parent) + goto handle_b_res_1; /* * Reserve some resources for CardBus. We reserve * a fixed amount of bus space for CardBus bridges. */ - b_res[0].start = 0; - b_res[0].flags |= IORESOURCE_IO | IORESOURCE_SIZEALIGN; - if (realloc_head) - add_to_list(realloc_head, bridge, b_res, pci_cardbus_io_size, 0 /* dont care */); + b_res[0].start = pci_cardbus_io_size; + b_res[0].end = b_res[0].start + pci_cardbus_io_size - 1; + b_res[0].flags |= IORESOURCE_IO | IORESOURCE_STARTALIGN; + if (realloc_head) { + b_res[0].end -= pci_cardbus_io_size; + add_to_list(realloc_head, bridge, b_res, pci_cardbus_io_size, + pci_cardbus_io_size); + } - b_res[1].start = 0; - b_res[1].flags |= IORESOURCE_IO | IORESOURCE_SIZEALIGN; - if (realloc_head) - add_to_list(realloc_head, bridge, b_res+1, pci_cardbus_io_size, 0 /* dont care */); +handle_b_res_1: + if (b_res[1].parent) + goto handle_b_res_2; + b_res[1].start = pci_cardbus_io_size; + b_res[1].end = b_res[1].start + pci_cardbus_io_size - 1; + b_res[1].flags |= IORESOURCE_IO | IORESOURCE_STARTALIGN; + if (realloc_head) { + b_res[1].end -= pci_cardbus_io_size; + add_to_list(realloc_head, bridge, b_res+1, pci_cardbus_io_size, + pci_cardbus_io_size); + } + +handle_b_res_2: + /* MEM1 must not be pref mmio */ + pci_read_config_word(bridge, PCI_CB_BRIDGE_CONTROL, &ctrl); + if (ctrl & PCI_CB_BRIDGE_CTL_PREFETCH_MEM1) { + ctrl &= ~PCI_CB_BRIDGE_CTL_PREFETCH_MEM1; + pci_write_config_word(bridge, PCI_CB_BRIDGE_CONTROL, ctrl); + pci_read_config_word(bridge, PCI_CB_BRIDGE_CONTROL, &ctrl); + } /* * Check whether prefetchable memory is supported @@ -785,38 +944,46 @@ static void pci_bus_size_cardbus(struct pci_bus *bus, pci_read_config_word(bridge, PCI_CB_BRIDGE_CONTROL, &ctrl); } + if (b_res[2].parent) + goto handle_b_res_3; /* * If we have prefetchable memory support, allocate * two regions. Otherwise, allocate one region of * twice the size. */ if (ctrl & PCI_CB_BRIDGE_CTL_PREFETCH_MEM0) { - b_res[2].start = 0; - b_res[2].flags |= IORESOURCE_MEM | IORESOURCE_PREFETCH | IORESOURCE_SIZEALIGN; - if (realloc_head) - add_to_list(realloc_head, bridge, b_res+2, pci_cardbus_mem_size, 0 /* dont care */); - - b_res[3].start = 0; - b_res[3].flags |= IORESOURCE_MEM | IORESOURCE_SIZEALIGN; - if (realloc_head) - add_to_list(realloc_head, bridge, b_res+3, pci_cardbus_mem_size, 0 /* dont care */); - } else { - b_res[3].start = 0; - b_res[3].flags |= IORESOURCE_MEM | IORESOURCE_SIZEALIGN; - if (realloc_head) - add_to_list(realloc_head, bridge, b_res+3, pci_cardbus_mem_size * 2, 0 /* dont care */); + b_res[2].start = pci_cardbus_mem_size; + b_res[2].end = b_res[2].start + pci_cardbus_mem_size - 1; + b_res[2].flags |= IORESOURCE_MEM | IORESOURCE_PREFETCH | + IORESOURCE_STARTALIGN; + if (realloc_head) { + b_res[2].end -= pci_cardbus_mem_size; + add_to_list(realloc_head, bridge, b_res+2, + pci_cardbus_mem_size, pci_cardbus_mem_size); + } + + /* reduce that to half */ + b_res_3_size = pci_cardbus_mem_size; + } + +handle_b_res_3: + if (b_res[3].parent) + goto handle_done; + b_res[3].start = pci_cardbus_mem_size; + b_res[3].end = b_res[3].start + b_res_3_size - 1; + b_res[3].flags |= IORESOURCE_MEM | IORESOURCE_STARTALIGN; + if (realloc_head) { + b_res[3].end -= b_res_3_size; + add_to_list(realloc_head, bridge, b_res+3, b_res_3_size, + pci_cardbus_mem_size); } - /* set the size of the resource to zero, so that the resource does not - * get assigned during required-resource allocation cycle but gets assigned - * during the optional-resource allocation cycle. - */ - b_res[0].start = b_res[1].start = b_res[2].start = b_res[3].start = 1; - b_res[0].end = b_res[1].end = b_res[2].end = b_res[3].end = 0; +handle_done: + ; } void __ref __pci_bus_size_bridges(struct pci_bus *bus, - struct resource_list_x *realloc_head) + struct list_head *realloc_head) { struct pci_dev *dev; unsigned long mask, prefmask; @@ -858,7 +1025,8 @@ void __ref __pci_bus_size_bridges(struct pci_bus *bus, * Follow thru */ default: - pbus_size_io(bus, 0, additional_io_size, realloc_head); + pbus_size_io(bus, realloc_head ? 0 : additional_io_size, + additional_io_size, realloc_head); /* If the bridge supports prefetchable range, size it separately. If it doesn't, or its prefetchable window has already been allocated by arch code, try @@ -866,11 +1034,15 @@ void __ref __pci_bus_size_bridges(struct pci_bus *bus, resources. */ mask = IORESOURCE_MEM; prefmask = IORESOURCE_MEM | IORESOURCE_PREFETCH; - if (pbus_size_mem(bus, prefmask, prefmask, 0, additional_mem_size, realloc_head)) + if (pbus_size_mem(bus, prefmask, prefmask, + realloc_head ? 0 : additional_mem_size, + additional_mem_size, realloc_head)) mask = prefmask; /* Success, size non-prefetch only. */ else additional_mem_size += additional_mem_size; - pbus_size_mem(bus, mask, IORESOURCE_MEM, 0, additional_mem_size, realloc_head); + pbus_size_mem(bus, mask, IORESOURCE_MEM, + realloc_head ? 0 : additional_mem_size, + additional_mem_size, realloc_head); break; } } @@ -882,8 +1054,8 @@ void __ref pci_bus_size_bridges(struct pci_bus *bus) EXPORT_SYMBOL(pci_bus_size_bridges); static void __ref __pci_bus_assign_resources(const struct pci_bus *bus, - struct resource_list_x *realloc_head, - struct resource_list_x *fail_head) + struct list_head *realloc_head, + struct list_head *fail_head) { struct pci_bus *b; struct pci_dev *dev; @@ -922,17 +1094,19 @@ void __ref pci_bus_assign_resources(const struct pci_bus *bus) EXPORT_SYMBOL(pci_bus_assign_resources); static void __ref __pci_bridge_assign_resources(const struct pci_dev *bridge, - struct resource_list_x *fail_head) + struct list_head *add_head, + struct list_head *fail_head) { struct pci_bus *b; - pdev_assign_resources_sorted((struct pci_dev *)bridge, fail_head); + pdev_assign_resources_sorted((struct pci_dev *)bridge, + add_head, fail_head); b = bridge->subordinate; if (!b) return; - __pci_bus_assign_resources(b, NULL, fail_head); + __pci_bus_assign_resources(b, add_head, fail_head); switch (bridge->class >> 8) { case PCI_CLASS_BRIDGE_PCI: @@ -1095,6 +1269,58 @@ static int __init pci_get_max_depth(void) return depth; } +/* + * -1: undefined, will auto detect later + * 0: disabled by user + * 1: disabled by auto detect + * 2: enabled by user + * 3: enabled by auto detect + */ +enum enable_type { + undefined = -1, + user_disabled, + auto_disabled, + user_enabled, + auto_enabled, +}; + +static enum enable_type pci_realloc_enable __initdata = undefined; +void __init pci_realloc_get_opt(char *str) +{ + if (!strncmp(str, "off", 3)) + pci_realloc_enable = user_disabled; + else if (!strncmp(str, "on", 2)) + pci_realloc_enable = user_enabled; +} +static bool __init pci_realloc_enabled(void) +{ + return pci_realloc_enable >= user_enabled; +} + +static void __init pci_realloc_detect(void) +{ +#if defined(CONFIG_PCI_IOV) && defined(CONFIG_PCI_REALLOC_ENABLE_AUTO) + struct pci_dev *dev = NULL; + + if (pci_realloc_enable != undefined) + return; + + for_each_pci_dev(dev) { + int i; + + for (i = PCI_IOV_RESOURCES; i <= PCI_IOV_RESOURCE_END; i++) { + struct resource *r = &dev->resource[i]; + + /* Not assigned, or rejected by kernel ? */ + if (r->flags && !r->start) { + pci_realloc_enable = auto_enabled; + + return; + } + } + } +#endif +} /* * first try will not touch pci bridge res @@ -1105,59 +1331,57 @@ void __init pci_assign_unassigned_resources(void) { struct pci_bus *bus; - struct resource_list_x realloc_list; /* list of resources that + LIST_HEAD(realloc_head); /* list of resources that want additional resources */ + struct list_head *add_list = NULL; int tried_times = 0; enum release_type rel_type = leaf_only; - struct resource_list_x head, *list; + LIST_HEAD(fail_head); + struct pci_dev_resource *fail_res; unsigned long type_mask = IORESOURCE_IO | IORESOURCE_MEM | IORESOURCE_PREFETCH; - unsigned long failed_type; - int max_depth = pci_get_max_depth(); - int pci_try_num; - + int pci_try_num = 1; - head.next = NULL; - realloc_list.next = NULL; + /* don't realloc if asked to do so */ + pci_realloc_detect(); + if (pci_realloc_enabled()) { + int max_depth = pci_get_max_depth(); - pci_try_num = max_depth + 1; - printk(KERN_DEBUG "PCI: max bus depth: %d pci_try_num: %d\n", - max_depth, pci_try_num); + pci_try_num = max_depth + 1; + printk(KERN_DEBUG "PCI: max bus depth: %d pci_try_num: %d\n", + max_depth, pci_try_num); + } again: + /* + * last try will use add_list, otherwise will try good to have as + * must have, so can realloc parent bridge resource + */ + if (tried_times + 1 == pci_try_num) + add_list = &realloc_head; /* Depth first, calculate sizes and alignments of all subordinate buses. */ list_for_each_entry(bus, &pci_root_buses, node) - __pci_bus_size_bridges(bus, &realloc_list); + __pci_bus_size_bridges(bus, add_list); /* Depth last, allocate resources and update the hardware. */ list_for_each_entry(bus, &pci_root_buses, node) - __pci_bus_assign_resources(bus, &realloc_list, &head); - BUG_ON(realloc_list.next); + __pci_bus_assign_resources(bus, add_list, &fail_head); + if (add_list) + BUG_ON(!list_empty(add_list)); tried_times++; /* any device complain? */ - if (!head.next) + if (list_empty(&fail_head)) goto enable_and_dump; - /* don't realloc if asked to do so */ - if (!pci_realloc_enabled()) { - free_list(resource_list_x, &head); - goto enable_and_dump; - } + if (tried_times >= pci_try_num) { + if (pci_realloc_enable == undefined) + printk(KERN_INFO "Some PCI device resources are unassigned, try booting with pci=realloc\n"); + else if (pci_realloc_enable == auto_enabled) + printk(KERN_INFO "Automatically enabled pci realloc, if you have problem, try booting with pci=realloc=off\n"); - failed_type = 0; - for (list = head.next; list;) { - failed_type |= list->flags; - list = list->next; - } - /* - * io port are tight, don't try extra - * or if reach the limit, don't want to try more - */ - failed_type &= type_mask; - if ((failed_type == IORESOURCE_IO) || (tried_times >= pci_try_num)) { - free_list(resource_list_x, &head); + free_list(&fail_head); goto enable_and_dump; } @@ -1172,25 +1396,23 @@ again: * Try to release leaf bridge's resources that doesn't fit resource of * child device under that bridge */ - for (list = head.next; list;) { - bus = list->dev->bus; - pci_bus_release_bridge_resources(bus, list->flags & type_mask, - rel_type); - list = list->next; + list_for_each_entry(fail_res, &fail_head, list) { + bus = fail_res->dev->bus; + pci_bus_release_bridge_resources(bus, + fail_res->flags & type_mask, + rel_type); } /* restore size and flags */ - for (list = head.next; list;) { - struct resource *res = list->res; + list_for_each_entry(fail_res, &fail_head, list) { + struct resource *res = fail_res->res; - res->start = list->start; - res->end = list->end; - res->flags = list->flags; - if (list->dev->subordinate) + res->start = fail_res->start; + res->end = fail_res->end; + res->flags = fail_res->flags; + if (fail_res->dev->subordinate) res->flags = 0; - - list = list->next; } - free_list(resource_list_x, &head); + free_list(&fail_head); goto again; @@ -1207,26 +1429,27 @@ enable_and_dump: void pci_assign_unassigned_bridge_resources(struct pci_dev *bridge) { struct pci_bus *parent = bridge->subordinate; + LIST_HEAD(add_list); /* list of resources that + want additional resources */ int tried_times = 0; - struct resource_list_x head, *list; + LIST_HEAD(fail_head); + struct pci_dev_resource *fail_res; int retval; unsigned long type_mask = IORESOURCE_IO | IORESOURCE_MEM | IORESOURCE_PREFETCH; - head.next = NULL; - again: - pci_bus_size_bridges(parent); - __pci_bridge_assign_resources(bridge, &head); - + __pci_bus_size_bridges(parent, &add_list); + __pci_bridge_assign_resources(bridge, &add_list, &fail_head); + BUG_ON(!list_empty(&add_list)); tried_times++; - if (!head.next) + if (list_empty(&fail_head)) goto enable_all; if (tried_times >= 2) { /* still fail, don't need to try more */ - free_list(resource_list_x, &head); + free_list(&fail_head); goto enable_all; } @@ -1237,27 +1460,24 @@ again: * Try to release leaf bridge's resources that doesn't fit resource of * child device under that bridge */ - for (list = head.next; list;) { - struct pci_bus *bus = list->dev->bus; - unsigned long flags = list->flags; + list_for_each_entry(fail_res, &fail_head, list) { + struct pci_bus *bus = fail_res->dev->bus; + unsigned long flags = fail_res->flags; pci_bus_release_bridge_resources(bus, flags & type_mask, whole_subtree); - list = list->next; } /* restore size and flags */ - for (list = head.next; list;) { - struct resource *res = list->res; + list_for_each_entry(fail_res, &fail_head, list) { + struct resource *res = fail_res->res; - res->start = list->start; - res->end = list->end; - res->flags = list->flags; - if (list->dev->subordinate) + res->start = fail_res->start; + res->end = fail_res->end; + res->flags = fail_res->flags; + if (fail_res->dev->subordinate) res->flags = 0; - - list = list->next; } - free_list(resource_list_x, &head); + free_list(&fail_head); goto again; @@ -1267,3 +1487,41 @@ enable_all: pci_enable_bridges(parent); } EXPORT_SYMBOL_GPL(pci_assign_unassigned_bridge_resources); + +#ifdef CONFIG_HOTPLUG +/** + * pci_rescan_bus - scan a PCI bus for devices. + * @bus: PCI bus to scan + * + * Scan a PCI bus and child buses for new devices, adds them, + * and enables them. + * + * Returns the max number of subordinate bus discovered. + */ +unsigned int __ref pci_rescan_bus(struct pci_bus *bus) +{ + unsigned int max; + struct pci_dev *dev; + LIST_HEAD(add_list); /* list of resources that + want additional resources */ + + max = pci_scan_child_bus(bus); + + down_read(&pci_bus_sem); + list_for_each_entry(dev, &bus->devices, bus_list) + if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE || + dev->hdr_type == PCI_HEADER_TYPE_CARDBUS) + if (dev->subordinate) + __pci_bus_size_bridges(dev->subordinate, + &add_list); + up_read(&pci_bus_sem); + __pci_bus_assign_resources(bus, &add_list, NULL); + BUG_ON(!list_empty(&add_list)); + + pci_enable_bridges(bus); + pci_bus_add_devices(bus); + + return max; +} +EXPORT_SYMBOL_GPL(pci_rescan_bus); +#endif |