Merge branch 'pci/resource'

- Realign space as required by bridge windows after dividing it up (Mika
  Westerberg)

- Account for space required by other devices on the bus before
  distributing it all to bridges (Mika Westerberg)

- Distribute spare resources to root bus devices as well as to other
  hotplug bridges (Mika Westerberg)

- Fix bug that dropped root bus resources that end at zero, e.g., a host
  bridge that leads only to bus 00 (Geert Uytterhoeven)

* pci/resource:
  PCI: Fix dropping valid root bus resources with .end = zero
  PCI: Distribute available resources for root buses, too
  PCI: Take other bus devices into account when distributing resources
  PCI: Align extra resources for hotplug bridges properly
This commit is contained in:
Bjorn Helgaas 2023-02-22 13:47:27 -06:00
commit ebdce9e3d0
2 changed files with 177 additions and 73 deletions

View File

@ -996,7 +996,7 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge)
resource_list_for_each_entry_safe(window, n, &resources) {
offset = window->offset;
res = window->res;
if (!res->end)
if (!res->flags && !res->start && !res->end)
continue;
list_move_tail(&window->node, &bridge->windows);

View File

@ -1765,12 +1765,70 @@ static void adjust_bridge_window(struct pci_dev *bridge, struct resource *res,
add_size = size - new_size;
pci_dbg(bridge, "bridge window %pR shrunken by %pa\n", res,
&add_size);
} else {
return;
}
res->end = res->start + new_size - 1;
remove_from_list(add_list, res);
/* If the resource is part of the add_list, remove it now */
if (add_list)
remove_from_list(add_list, res);
}
static void remove_dev_resource(struct resource *avail, struct pci_dev *dev,
struct resource *res)
{
resource_size_t size, align, tmp;
size = resource_size(res);
if (!size)
return;
align = pci_resource_alignment(dev, res);
align = align ? ALIGN(avail->start, align) - avail->start : 0;
tmp = align + size;
avail->start = min(avail->start + tmp, avail->end + 1);
}
static void remove_dev_resources(struct pci_dev *dev, struct resource *io,
struct resource *mmio,
struct resource *mmio_pref)
{
int i;
for (i = 0; i < PCI_NUM_RESOURCES; i++) {
struct resource *res = &dev->resource[i];
if (resource_type(res) == IORESOURCE_IO) {
remove_dev_resource(io, dev, res);
} else if (resource_type(res) == IORESOURCE_MEM) {
/*
* Make sure prefetchable memory is reduced from
* the correct resource. Specifically we put 32-bit
* prefetchable memory in non-prefetchable window
* if there is an 64-bit pretchable window.
*
* See comments in __pci_bus_size_bridges() for
* more information.
*/
if ((res->flags & IORESOURCE_PREFETCH) &&
((res->flags & IORESOURCE_MEM_64) ==
(mmio_pref->flags & IORESOURCE_MEM_64)))
remove_dev_resource(mmio_pref, dev, res);
else
remove_dev_resource(mmio, dev, res);
}
}
}
/*
* io, mmio and mmio_pref contain the total amount of bridge window space
* available. This includes the minimal space needed to cover all the
* existing devices on the bus and the possible extra space that can be
* shared with the bridges.
*/
static void pci_bus_distribute_available_resources(struct pci_bus *bus,
struct list_head *add_list,
struct resource io,
@ -1780,7 +1838,7 @@ static void pci_bus_distribute_available_resources(struct pci_bus *bus,
unsigned int normal_bridges = 0, hotplug_bridges = 0;
struct resource *io_res, *mmio_res, *mmio_pref_res;
struct pci_dev *dev, *bridge = bus->self;
resource_size_t io_per_hp, mmio_per_hp, mmio_pref_per_hp, align;
resource_size_t io_per_b, mmio_per_b, mmio_pref_per_b, align;
io_res = &bridge->resource[PCI_BRIDGE_IO_WINDOW];
mmio_res = &bridge->resource[PCI_BRIDGE_MEM_WINDOW];
@ -1824,94 +1882,88 @@ static void pci_bus_distribute_available_resources(struct pci_bus *bus,
normal_bridges++;
}
/*
* There is only one bridge on the bus so it gets all available
* resources which it can then distribute to the possible hotplug
* bridges below.
*/
if (hotplug_bridges + normal_bridges == 1) {
dev = list_first_entry(&bus->devices, struct pci_dev, bus_list);
if (dev->subordinate)
pci_bus_distribute_available_resources(dev->subordinate,
add_list, io, mmio, mmio_pref);
if (!(hotplug_bridges + normal_bridges))
return;
/*
* Calculate the amount of space we can forward from "bus" to any
* downstream buses, i.e., the space left over after assigning the
* BARs and windows on "bus".
*/
list_for_each_entry(dev, &bus->devices, bus_list) {
if (!dev->is_virtfn)
remove_dev_resources(dev, &io, &mmio, &mmio_pref);
}
if (hotplug_bridges == 0)
return;
/*
* 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.
* If there is at least one hotplug bridge on this bus it gets all
* the extra resource space that was left after the reductions
* above.
*
* If there are no hotplug bridges the extra resource space is
* split between non-hotplug bridges. This is to allow possible
* hotplug bridges below them to get the extra space as well.
*/
if (hotplug_bridges) {
io_per_b = div64_ul(resource_size(&io), hotplug_bridges);
mmio_per_b = div64_ul(resource_size(&mmio), hotplug_bridges);
mmio_pref_per_b = div64_ul(resource_size(&mmio_pref),
hotplug_bridges);
} else {
io_per_b = div64_ul(resource_size(&io), normal_bridges);
mmio_per_b = div64_ul(resource_size(&mmio), normal_bridges);
mmio_pref_per_b = div64_ul(resource_size(&mmio_pref),
normal_bridges);
}
for_each_pci_bridge(dev, bus) {
resource_size_t used_size;
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_IO_WINDOW];
align = pci_resource_alignment(dev, res);
align = align ? ALIGN(io.start, align) - io.start : 0;
used_size = align + resource_size(res);
if (!res->parent)
io.start = min(io.start + used_size, io.end + 1);
res = &dev->resource[PCI_BRIDGE_MEM_WINDOW];
align = pci_resource_alignment(dev, res);
align = align ? ALIGN(mmio.start, align) - mmio.start : 0;
used_size = align + resource_size(res);
if (!res->parent)
mmio.start = min(mmio.start + used_size, mmio.end + 1);
res = &dev->resource[PCI_BRIDGE_PREF_MEM_WINDOW];
align = pci_resource_alignment(dev, res);
align = align ? ALIGN(mmio_pref.start, align) -
mmio_pref.start : 0;
used_size = align + resource_size(res);
if (!res->parent)
mmio_pref.start = min(mmio_pref.start + used_size,
mmio_pref.end + 1);
}
io_per_hp = div64_ul(resource_size(&io), hotplug_bridges);
mmio_per_hp = div64_ul(resource_size(&mmio), hotplug_bridges);
mmio_pref_per_hp = div64_ul(resource_size(&mmio_pref),
hotplug_bridges);
/*
* 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 || !dev->is_hotplug_bridge)
if (!b)
continue;
if (hotplug_bridges && !dev->is_hotplug_bridge)
continue;
res = &dev->resource[PCI_BRIDGE_IO_WINDOW];
/*
* Distribute available extra resources equally between
* hotplug-capable downstream ports taking alignment into
* account.
* Make sure the split resource space is properly aligned
* for bridge windows (align it down to avoid going above
* what is available).
*/
io.end = io.start + io_per_hp - 1;
mmio.end = mmio.start + mmio_per_hp - 1;
mmio_pref.end = mmio_pref.start + mmio_pref_per_hp - 1;
align = pci_resource_alignment(dev, res);
io.end = align ? io.start + ALIGN_DOWN(io_per_b, align) - 1
: io.start + io_per_b - 1;
/*
* The x_per_b holds the extra resource space that can be
* added for each bridge but there is the minimal already
* reserved as well so adjust x.start down accordingly to
* cover the whole space.
*/
io.start -= resource_size(res);
res = &dev->resource[PCI_BRIDGE_MEM_WINDOW];
align = pci_resource_alignment(dev, res);
mmio.end = align ? mmio.start + ALIGN_DOWN(mmio_per_b, align) - 1
: mmio.start + mmio_per_b - 1;
mmio.start -= resource_size(res);
res = &dev->resource[PCI_BRIDGE_PREF_MEM_WINDOW];
align = pci_resource_alignment(dev, res);
mmio_pref.end = align ? mmio_pref.start +
ALIGN_DOWN(mmio_pref_per_b, align) - 1
: mmio_pref.start + mmio_pref_per_b - 1;
mmio_pref.start -= resource_size(res);
pci_bus_distribute_available_resources(b, add_list, io, mmio,
mmio_pref);
io.start += io_per_hp;
mmio.start += mmio_per_hp;
mmio_pref.start += mmio_pref_per_hp;
io.start += io.end + 1;
mmio.start += mmio.end + 1;
mmio_pref.start += mmio_pref.end + 1;
}
}
@ -1923,6 +1975,8 @@ static void pci_bridge_distribute_available_resources(struct pci_dev *bridge,
if (!bridge->is_hotplug_bridge)
return;
pci_dbg(bridge, "distributing available resources\n");
/* Take the initial extra resources from the hotplug port */
available_io = bridge->resource[PCI_BRIDGE_IO_WINDOW];
available_mmio = bridge->resource[PCI_BRIDGE_MEM_WINDOW];
@ -1934,6 +1988,54 @@ static void pci_bridge_distribute_available_resources(struct pci_dev *bridge,
available_mmio_pref);
}
static bool pci_bridge_resources_not_assigned(struct pci_dev *dev)
{
const struct resource *r;
/*
* If the child device's resources are not yet assigned it means we
* are configuring them (not the boot firmware), so we should be
* able to extend the upstream bridge resources in the same way we
* do with the normal hotplug case.
*/
r = &dev->resource[PCI_BRIDGE_IO_WINDOW];
if (r->flags && !(r->flags & IORESOURCE_STARTALIGN))
return false;
r = &dev->resource[PCI_BRIDGE_MEM_WINDOW];
if (r->flags && !(r->flags & IORESOURCE_STARTALIGN))
return false;
r = &dev->resource[PCI_BRIDGE_PREF_MEM_WINDOW];
if (r->flags && !(r->flags & IORESOURCE_STARTALIGN))
return false;
return true;
}
static void
pci_root_bus_distribute_available_resources(struct pci_bus *bus,
struct list_head *add_list)
{
struct pci_dev *dev, *bridge = bus->self;
for_each_pci_bridge(dev, bus) {
struct pci_bus *b;
b = dev->subordinate;
if (!b)
continue;
/*
* Need to check "bridge" here too because it is NULL
* in case of root bus.
*/
if (bridge && pci_bridge_resources_not_assigned(dev))
pci_bridge_distribute_available_resources(bridge,
add_list);
else
pci_root_bus_distribute_available_resources(b, add_list);
}
}
/*
* First try will not touch PCI bridge res.
* Second and later try will clear small leaf bridge res.
@ -1973,6 +2075,8 @@ again:
*/
__pci_bus_size_bridges(bus, add_list);
pci_root_bus_distribute_available_resources(bus, add_list);
/* Depth last, allocate resources and update the hardware. */
__pci_bus_assign_resources(bus, add_list, &fail_head);
if (add_list)