Skip to content

Commit

Permalink
Merge branch 'pci/resource'
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
bjorn-helgaas committed Feb 22, 2023
2 parents 0b7af1d + 9d8ba74 commit ebdce9e
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 67 deletions.
2 changes: 1 addition & 1 deletion drivers/pci/probe.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
236 changes: 170 additions & 66 deletions drivers/pci/setup-bus.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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];
Expand Down Expand Up @@ -1824,94 +1882,88 @@ static void pci_bus_distribute_available_resources(struct pci_bus *bus,
normal_bridges++;
}

if (!(hotplug_bridges + normal_bridges))
return;

/*
* 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.
* 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".
*/
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);
return;
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;
struct pci_bus *b;

if (dev->is_hotplug_bridge)
b = dev->subordinate;
if (!b)
continue;
if (hotplug_bridges && !dev->is_hotplug_bridge)
continue;

res = &dev->resource[PCI_BRIDGE_IO_WINDOW];

/*
* Reduce the available resource space by what the
* bridge and devices below it occupy.
* Make sure the split resource space is properly aligned
* for bridge windows (align it down to avoid going above
* what is available).
*/
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);
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);
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);
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);
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)
continue;

/*
* Distribute available extra resources equally between
* hotplug-capable downstream ports taking alignment into
* account.
*/
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;
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;
}
}

Expand All @@ -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];
Expand All @@ -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.
Expand Down Expand Up @@ -1973,6 +2075,8 @@ void pci_assign_unassigned_root_bus_resources(struct pci_bus *bus)
*/
__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)
Expand Down

0 comments on commit ebdce9e

Please sign in to comment.