diff options
Diffstat (limited to 'drivers/pci/setup-bus.c')
-rw-r--r-- | drivers/pci/setup-bus.c | 184 |
1 files changed, 180 insertions, 4 deletions
diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index 6826a893288a..b1ad466199ad 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -1854,6 +1854,175 @@ void __init pci_assign_unassigned_resources(void) } } +static void extend_bridge_window(struct pci_dev *bridge, struct resource *res, + struct list_head *add_list, resource_size_t available) +{ + struct pci_dev_resource *dev_res; + + if (res->parent) + return; + + if (resource_size(res) >= available) + return; + + dev_res = res_to_dev_res(add_list, res); + if (!dev_res) + return; + + /* Is there room to extend the window? */ + if (available - resource_size(res) <= dev_res->add_size) + return; + + dev_res->add_size = available - resource_size(res); + dev_dbg(&bridge->dev, "bridge window %pR extended by %pa\n", res, + &dev_res->add_size); +} + +static void pci_bus_distribute_available_resources(struct pci_bus *bus, + struct list_head *add_list, resource_size_t available_io, + resource_size_t available_mmio, resource_size_t available_mmio_pref) +{ + resource_size_t remaining_io, remaining_mmio, remaining_mmio_pref; + unsigned int normal_bridges = 0, hotplug_bridges = 0; + struct resource *io_res, *mmio_res, *mmio_pref_res; + struct pci_dev *dev, *bridge = bus->self; + + io_res = &bridge->resource[PCI_BRIDGE_RESOURCES + 0]; + mmio_res = &bridge->resource[PCI_BRIDGE_RESOURCES + 1]; + mmio_pref_res = &bridge->resource[PCI_BRIDGE_RESOURCES + 2]; + + /* + * Update additional resource list (add_list) to fill all the + * extra resource space available for this port except the space + * calculated in __pci_bus_size_bridges() which covers all the + * devices currently connected to the port and below. + */ + extend_bridge_window(bridge, io_res, add_list, available_io); + extend_bridge_window(bridge, mmio_res, add_list, available_mmio); + extend_bridge_window(bridge, mmio_pref_res, add_list, + available_mmio_pref); + + /* + * Calculate the total amount of extra resource space we can + * pass to bridges below this one. This is basically the + * extra space reduced by the minimal required space for the + * non-hotplug bridges. + */ + remaining_io = available_io; + remaining_mmio = available_mmio; + remaining_mmio_pref = available_mmio_pref; + + /* + * Calculate how many hotplug bridges and normal bridges there + * are on this bus. We will distribute the additional available + * resources between hotplug bridges. + */ + for_each_pci_bridge(dev, bus) { + if (dev->is_hotplug_bridge) + hotplug_bridges++; + else + normal_bridges++; + } + + for_each_pci_bridge(dev, bus) { + const struct resource *res; + + if (dev->is_hotplug_bridge) + continue; + + /* + * Reduce the available resource space by what the + * bridge and devices below it occupy. + */ + res = &dev->resource[PCI_BRIDGE_RESOURCES + 0]; + if (!res->parent && available_io > resource_size(res)) + remaining_io -= resource_size(res); + + res = &dev->resource[PCI_BRIDGE_RESOURCES + 1]; + if (!res->parent && available_mmio > resource_size(res)) + remaining_mmio -= resource_size(res); + + res = &dev->resource[PCI_BRIDGE_RESOURCES + 2]; + if (!res->parent && available_mmio_pref > resource_size(res)) + remaining_mmio_pref -= resource_size(res); + } + + /* + * Go over devices on this bus and distribute the remaining + * resource space between hotplug bridges. + */ + for_each_pci_bridge(dev, bus) { + struct pci_bus *b; + + b = dev->subordinate; + if (!b) + continue; + + if (!hotplug_bridges && normal_bridges == 1) { + /* + * There is only one bridge on the bus (upstream + * port) so it gets all available resources + * which it can then distribute to the possible + * hotplug bridges below. + */ + pci_bus_distribute_available_resources(b, add_list, + available_io, available_mmio, + available_mmio_pref); + } else if (dev->is_hotplug_bridge) { + resource_size_t align, io, mmio, mmio_pref; + + /* + * Distribute available extra resources equally + * between hotplug-capable downstream ports + * taking alignment into account. + * + * Here hotplug_bridges is always != 0. + */ + align = pci_resource_alignment(bridge, io_res); + io = div64_ul(available_io, hotplug_bridges); + io = min(ALIGN(io, align), remaining_io); + remaining_io -= io; + + align = pci_resource_alignment(bridge, mmio_res); + mmio = div64_ul(available_mmio, hotplug_bridges); + mmio = min(ALIGN(mmio, align), remaining_mmio); + remaining_mmio -= mmio; + + align = pci_resource_alignment(bridge, mmio_pref_res); + mmio_pref = div64_ul(available_mmio_pref, + hotplug_bridges); + mmio_pref = min(ALIGN(mmio_pref, align), + remaining_mmio_pref); + remaining_mmio_pref -= mmio_pref; + + pci_bus_distribute_available_resources(b, add_list, io, + mmio, mmio_pref); + } + } +} + +static void +pci_bridge_distribute_available_resources(struct pci_dev *bridge, + struct list_head *add_list) +{ + resource_size_t available_io, available_mmio, available_mmio_pref; + const struct resource *res; + + if (!bridge->is_hotplug_bridge) + return; + + /* Take the initial extra resources from the hotplug port */ + res = &bridge->resource[PCI_BRIDGE_RESOURCES + 0]; + available_io = resource_size(res); + res = &bridge->resource[PCI_BRIDGE_RESOURCES + 1]; + available_mmio = resource_size(res); + res = &bridge->resource[PCI_BRIDGE_RESOURCES + 2]; + available_mmio_pref = resource_size(res); + + pci_bus_distribute_available_resources(bridge->subordinate, + add_list, available_io, available_mmio, available_mmio_pref); +} + void pci_assign_unassigned_bridge_resources(struct pci_dev *bridge) { struct pci_bus *parent = bridge->subordinate; @@ -1866,6 +2035,14 @@ void pci_assign_unassigned_bridge_resources(struct pci_dev *bridge) again: __pci_bus_size_bridges(parent, &add_list); + + /* + * Distribute remaining resources (if any) equally between + * hotplug bridges below. This makes it possible to extend the + * hierarchy later without running out of resources. + */ + pci_bridge_distribute_available_resources(bridge, &add_list); + __pci_bridge_assign_resources(bridge, &add_list, &fail_head); BUG_ON(!list_empty(&add_list)); tried_times++; @@ -2018,10 +2195,9 @@ void pci_assign_unassigned_bus_resources(struct pci_bus *bus) want additional resources */ down_read(&pci_bus_sem); - list_for_each_entry(dev, &bus->devices, bus_list) - if (pci_is_bridge(dev) && pci_has_subordinate(dev)) - __pci_bus_size_bridges(dev->subordinate, - &add_list); + for_each_pci_bridge(dev, bus) + if (pci_has_subordinate(dev)) + __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)); |