allocator_v4: Use memranges only for toplevel

During phase 1 of the resource allocation we gather all the size
requirements. Starting from the leafs of our devicetree, we cal-
culate the requirements per bus, until we reach the resource do-
main.

However, because alignment plays a role, we can't just accumulate
the sizes of all resources on a bus. Instead, we already sort all
the resources per bus to predict their relative placement, inclu-
ding alignment gaps. Then, phase 2 has to perform the final allo-
cations with the exact same relative placement.

This patch introduces a very simple mechanism to avoid repeating
all the calculations: In phase 1, we note the relative `base` of
each resource on a bus. And after we allocated all the resources
directly below the domain in phase 2, we add the absolute `base`
of bridge resources to the relative `base` of child resources.

This saves most of the computational complexity in phase 2. How-
ever, with a shallow devicetree with most devices directly below
the domain, this won't have a measurable impact.

Example after phase 1:

  domain
    |
    `-- bridge #0
          |   res #0, base 0x000000 (relative),
          |   size 12M, align 8M
          |
          |-- device #0
          |         res #1, base 0x800000 (relative),
          |         size 4M, align 4M
          |
          `-- bridge #1
                |   res #2, base 0x000000 (relative),
                |   size 8M, align 8M
                |
                `-- device #1
                          res #3, base 0x000000 (relative),
                          size 8M, align 8M

After phase 2 allocation at the domain level (assuming res #0 got
0xa000000 assigned):

  domain
    |
    `-- bridge #0
          |   res #0, base 0xa000000 (absolute),
          |   size 12M, align 8M
          |
          |-- device #0
          |         res #1, base 0x800000 (relative),
          |         size 4M, align 4M
          |
          `-- bridge #1
                |   res #2, base 0x000000 (relative),
                |   size 8M, align 8M
                |
                `-- device #1
                          res #3, base 0x000000 (relative),
                          size 8M, align 8M

Now, all we need to do is to add the `base` of bridge resources
recursively. Starting with resources on the bus below bridge #0:

  domain
    |
    `-- bridge #0
          |   res #0, base 0xa000000 (absolute),
          |   size 12M, align 8M
          |
          |-- device #0
          |         res #1, base 0xa800000 (absolute),
          |         size 4M, align 4M
          |
          `-- bridge #1
                |   res #2, base 0xa000000 (absolute),
                |   size 8M, align 8M
                |
                `-- device #1
                          res #3, base 0x000000 (relative),
                          size 8M, align 8M

And finally for resources on the bus below bridge #1:

  domain
    |
    `-- bridge #0
          |   res #0, base 0xa000000 (absolute),
          |   size 12M, align 8M
          |
          |-- device #0
          |         res #1, base 0xa800000 (absolute),
          |         size 4M, align 4M
          |
          `-- bridge #1
                |   res #2, base 0xa000000 (absolute),
                |   size 8M, align 8M
                |
                `-- device #1
                          res #3, base 0xa000000 (absolute),
                          size 8M, align 8M

Change-Id: I70c700318a85f6760f27597730bc9c9a86dbe6b3
Signed-off-by: Nico Huber <nico.h@gmx.de>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/65420
Reviewed-by: Lean Sheng Tan <sheng.tan@9elements.com>
Reviewed-by: Arthur Heymans <arthur@aheymans.xyz>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Felix Singer <service+coreboot-gerrit@felixsinger.de>
This commit is contained in:
Nico Huber 2020-05-23 23:20:13 +02:00 committed by Arthur Heymans
parent 5226301765
commit 9260ea60bf
1 changed files with 123 additions and 125 deletions

View File

@ -49,6 +49,11 @@ static resource_t effective_limit(const struct resource *const res)
* resource. This is required to guarantee that the upstream bridge/ * resource. This is required to guarantee that the upstream bridge/
* domain honors the limit and alignment requirements for this bridge * domain honors the limit and alignment requirements for this bridge
* based on the tightest constraints downstream. * based on the tightest constraints downstream.
*
* Last but not least, it stores the offset inside the bridge resource
* for each child resource in its base field. This simplifies pass 2
* for resources behind a bridge, as we only have to add offsets to the
* allocated base of the bridge resource.
*/ */
static void update_bridge_resource(const struct device *bridge, struct resource *bridge_res, static void update_bridge_resource(const struct device *bridge, struct resource *bridge_res,
unsigned long type_match, int print_depth) unsigned long type_match, int print_depth)
@ -89,13 +94,8 @@ static void update_bridge_resource(const struct device *bridge, struct resource
/* /*
* Propagate the resource alignment to the bridge resource. The * Propagate the resource alignment to the bridge resource. The
* condition can only be true for the first (largest) resource. For all * condition can only be true for the first (largest) resource. For all
* other children resources, alignment is taken care of by updating the * other child resources, alignment is taken care of by rounding their
* base to round up as per the child resource alignment. It is * base up.
* guaranteed that pass 2 follows the exact same method of picking the
* resource for allocation using largest_resource(). Thus, as long as
* the alignment for the largest child resource is propagated up to the
* bridge resource, it can be guaranteed that the alignment for all
* resources is appropriately met.
*/ */
if (child_res->align > bridge_res->align) if (child_res->align > bridge_res->align)
bridge_res->align = child_res->align; bridge_res->align = child_res->align;
@ -103,10 +103,8 @@ static void update_bridge_resource(const struct device *bridge, struct resource
/* /*
* Propagate the resource limit to the bridge resource. If a downstream * Propagate the resource limit to the bridge resource. If a downstream
* device has stricter requirements w.r.t. limits for any resource, that * device has stricter requirements w.r.t. limits for any resource, that
* constraint needs to be propagated back up to the downstream bridges * constraint needs to be propagated back up to the bridges downstream
* of the domain. This guarantees that the resource allocation which * of the domain. This way, the whole bridge resource fulfills the limit.
* starts at the domain level takes into account all these constraints
* thus working on a global view.
*/ */
if (effective_limit(child_res) < bridge_res->limit) if (effective_limit(child_res) < bridge_res->limit)
bridge_res->limit = effective_limit(child_res); bridge_res->limit = effective_limit(child_res);
@ -117,6 +115,14 @@ static void update_bridge_resource(const struct device *bridge, struct resource
*/ */
base = ALIGN_UP(base, POWER_OF_2(child_res->align)); base = ALIGN_UP(base, POWER_OF_2(child_res->align));
/*
* Store the relative offset inside the bridge resource for later
* consumption in allocate_bridge_resources(), and invalidate flags
* related to the base.
*/
child_res->base = base;
child_res->flags &= ~(IORESOURCE_ASSIGNED | IORESOURCE_STORED);
res_printk(print_depth + 1, "%s %02lx * [0x%llx - 0x%llx] %s\n", res_printk(print_depth + 1, "%s %02lx * [0x%llx - 0x%llx] %s\n",
dev_path(child), child_res->index, base, base + child_res->size - 1, dev_path(child), child_res->index, base, base + child_res->size - 1,
resource2str(child_res)); resource2str(child_res));
@ -237,48 +243,6 @@ static void print_resource_ranges(const struct device *dev, const struct memrang
} }
} }
/*
* This is where the actual allocation of resources happens during
* pass 2. Given the list of memory ranges corresponding to the
* resource of given type, it finds the biggest unallocated resource
* using the type mask on the downstream bus. This continues in a
* descending order until all resources of given type are allocated
* address space within the current resource window.
*/
static void allocate_child_resources(struct bus *bus, struct memranges *ranges,
unsigned long type_mask, unsigned long type_match)
{
const bool allocate_top_down =
bus->dev->path.type == DEVICE_PATH_DOMAIN &&
CONFIG(RESOURCE_ALLOCATION_TOP_DOWN);
struct resource *resource = NULL;
const struct device *dev;
while ((dev = largest_resource(bus, &resource, type_mask, type_match))) {
if (!resource->size)
continue;
if (memranges_steal(ranges, effective_limit(resource), resource->size,
resource->align, type_match, &resource->base,
allocate_top_down) == false) {
printk(BIOS_ERR, "Resource didn't fit!!! ");
printk(BIOS_DEBUG, " %s %02lx * size: 0x%llx limit: %llx %s\n",
dev_path(dev), resource->index, resource->size,
effective_limit(resource), resource2str(resource));
continue;
}
resource->limit = resource->base + resource->size - 1;
resource->flags |= IORESOURCE_ASSIGNED;
printk(BIOS_DEBUG, " %s %02lx * [0x%llx - 0x%llx] limit: %llx %s\n",
dev_path(dev), resource->index, resource->base,
resource->size ? resource->base + resource->size - 1 :
resource->base, resource->limit, resource2str(resource));
}
}
static void update_constraints(struct memranges *ranges, const struct device *dev, static void update_constraints(struct memranges *ranges, const struct device *dev,
const struct resource *res) const struct resource *res)
{ {
@ -348,49 +312,36 @@ static void constrain_domain_resources(const struct device *domain, struct memra
/* /*
* This function creates a list of memranges of given type using the * This function creates a list of memranges of given type using the
* resource that is provided. If the given resource is unassigned or if * resource that is provided. It applies additional constraints to
* the resource window size is 0, then it creates an empty list. This * ensure that the memranges do not overlap any of the fixed resources
* results in resource allocation for that resource type failing for * under the domain. The domain typically provides a memrange for the
* all downstream devices since there is nothing to allocate from. * entire address space. Thus, it is up to the chipset to add DRAM and
* * all other windows which cannot be used for resource allocation as
* In case of domain, it applies additional constraints to ensure that * fixed resources.
* the memranges do not overlap any of the fixed resources under that
* domain. Domain typically seems to provide memrange for entire address
* space. Thus, it is up to the chipset to add DRAM and all other
* windows which cannot be used for resource allocation as fixed
* resources.
*/ */
static void setup_resource_ranges(const struct device *dev, unsigned long type, static void setup_resource_ranges(const struct device *const domain,
struct memranges *ranges) const unsigned long type,
struct memranges *const ranges)
{ {
const unsigned long type_mask = IORESOURCE_TYPE_MASK | IORESOURCE_FIXED | const unsigned long type_mask = IORESOURCE_TYPE_MASK | IORESOURCE_FIXED;
(dev->path.type != DEVICE_PATH_DOMAIN
? IORESOURCE_PREFETCH | IORESOURCE_ASSIGNED
: 0);
const unsigned long type_match = type |
(dev->path.type != DEVICE_PATH_DOMAIN ? IORESOURCE_ASSIGNED : 0);
const unsigned char alignment = get_alignment_by_resource_type(type); const unsigned char alignment = get_alignment_by_resource_type(type);
memranges_init_empty_with_alignment(ranges, NULL, 0, alignment); memranges_init_empty_with_alignment(ranges, NULL, 0, alignment);
for (struct resource *res = dev->resource_list; res != NULL; res = res->next) { for (struct resource *res = domain->resource_list; res != NULL; res = res->next) {
if ((res->flags & type_mask) != type_match) if ((res->flags & type_mask) != type)
continue; continue;
printk(BIOS_DEBUG, "%s %s: base: %llx size: %llx align: %u gran: %u limit: %llx\n", printk(BIOS_DEBUG, "%s %s: base: %llx size: %llx align: %u gran: %u limit: %llx\n",
dev_path(dev), resource2str(res), res->base, res->size, res->align, dev_path(domain), resource2str(res), res->base, res->size, res->align,
res->gran, res->limit); res->gran, res->limit);
memranges_insert(ranges, res->base, res->limit - res->base + 1, type); memranges_insert(ranges, res->base, res->limit - res->base + 1, type);
if (dev->path.type != DEVICE_PATH_DOMAIN)
break; /* only one resource per type for bridges */
} }
if (dev->path.type == DEVICE_PATH_DOMAIN) constrain_domain_resources(domain, ranges, type);
constrain_domain_resources(dev, ranges, type);
print_resource_ranges(dev, ranges); print_resource_ranges(domain, ranges);
} }
static void print_resource_done(const struct device *dev, const struct resource *res) static void print_resource_done(const struct device *dev, const struct resource *res)
@ -413,49 +364,104 @@ static void cleanup_domain_resource_ranges(const struct device *dev, struct memr
} }
} }
static void assign_resource(struct resource *const res, const resource_t base,
const struct device *const dev)
{
res->base = base;
res->limit = res->base + res->size - 1;
res->flags |= IORESOURCE_ASSIGNED;
res->flags &= ~IORESOURCE_STORED;
printk(BIOS_DEBUG, " %s %02lx * [0x%llx - 0x%llx] limit: %llx %s\n",
dev_path(dev), res->index, res->base, res->limit, res->limit, resource2str(res));
}
/*
* This is where the actual allocation of resources happens during
* pass 2. We construct a list of memory ranges corresponding to the
* resource of a given type, then look for the biggest unallocated
* resource on the downstream bus. This continues in a descending order
* until all resources of a given type have space allocated within the
* domain's resource window.
*/
static void allocate_toplevel_resources(const struct device *const domain,
const unsigned long type)
{
const unsigned long type_mask = IORESOURCE_TYPE_MASK;
struct resource *res = NULL;
const struct device *dev;
struct memranges ranges;
resource_t base;
if (!dev_has_children(domain))
return;
setup_resource_ranges(domain, type, &ranges);
while ((dev = largest_resource(domain->link_list, &res, type_mask, type))) {
if (!res->size)
continue;
if (!memranges_steal(&ranges, res->limit, res->size, res->align, type, &base,
CONFIG(RESOURCE_ALLOCATION_TOP_DOWN))) {
printk(BIOS_ERR, "Resource didn't fit!!! ");
printk(BIOS_DEBUG, " %s %02lx * size: 0x%llx limit: %llx %s\n",
dev_path(dev), res->index, res->size,
res->limit, resource2str(res));
continue;
}
assign_resource(res, base, dev);
}
cleanup_domain_resource_ranges(domain, &ranges, type);
}
/* /*
* Pass 2 of the resource allocator at the bridge level loops through * Pass 2 of the resource allocator at the bridge level loops through
* all the resources for the bridge and generates a list of memory * all the resources for the bridge and assigns all the base addresses
* ranges similar to that at the domain level. However, there is no need * of its children's resources of the same type. update_bridge_resource()
* to apply any additional constraints since the window allocated to the * of pass 1 pre-calculated the offsets of these bases inside the bridge
* bridge is guaranteed to be non-overlapping by the allocator at domain * resource. Now that the bridge resource is allocated, all we have to
* level. * do is to add its final base to these offsets.
*
* Allocation at the bridge level works the same as at domain level
* (starts with the biggest resource requirement from downstream devices
* and continues in descending order). One major difference at the
* bridge level is that it considers prefmem resources separately from
* mem resources.
* *
* Once allocation at the current bridge is complete, resource allocator * Once allocation at the current bridge is complete, resource allocator
* continues walking down the downstream bridges until it hits the leaf * continues walking down the downstream bridges until it hits the leaf
* devices. * devices.
*/ */
static void assign_resource_cb(void *param, struct device *dev, struct resource *res)
{
/* We have to filter the same resources as update_bridge_resource(). */
if (!res->size || !res->limit)
return;
assign_resource(res, *(const resource_t *)param + res->base, dev);
}
static void allocate_bridge_resources(const struct device *bridge) static void allocate_bridge_resources(const struct device *bridge)
{ {
struct memranges ranges; const unsigned long type_mask =
const struct resource *res; IORESOURCE_TYPE_MASK | IORESOURCE_PREFETCH | IORESOURCE_FIXED;
struct bus *bus = bridge->link_list; struct bus *const bus = bridge->link_list;
unsigned long type_match; struct resource *res;
struct device *child; struct device *child;
const unsigned long type_mask = IORESOURCE_TYPE_MASK | IORESOURCE_PREFETCH;
for (res = bridge->resource_list; res; res = res->next) { for (res = bridge->resource_list; res != NULL; res = res->next) {
if (!res->size) if (!res->size)
continue; continue;
if (!(res->flags & IORESOURCE_BRIDGE)) if (!(res->flags & IORESOURCE_BRIDGE))
continue; continue;
type_match = res->flags & type_mask; if (!(res->flags & IORESOURCE_ASSIGNED))
continue;
setup_resource_ranges(bridge, type_match, &ranges); /* Run assign_resource_cb() for all downstream resources of the same type. */
allocate_child_resources(bus, &ranges, type_mask, type_match); search_bus_resources(bus, type_mask, res->flags & type_mask,
print_resource_done(bridge, res); assign_resource_cb, &res->base);
memranges_teardown(&ranges);
} }
for (child = bus->children; child; child = child->sibling) { for (child = bus->children; child != NULL; child = child->sibling) {
if (!dev_has_children(child)) if (!dev_has_children(child))
continue; continue;
@ -473,19 +479,13 @@ static void allocate_bridge_resources(const struct device *bridge)
* resource requirements of the downstream devices. * resource requirements of the downstream devices.
* *
* Once resources are allocated to all downstream devices of the domain, * Once resources are allocated to all downstream devices of the domain,
* it walks down each downstream bridge to continue the same process * it walks down each downstream bridge to finish resource assignment
* until resources are allocated to all devices under the domain. * of its children resources within its own window.
*/ */
static void allocate_domain_resources(const struct device *domain) static void allocate_domain_resources(const struct device *domain)
{ {
struct memranges ranges;
struct device *child;
/* Resource type I/O */ /* Resource type I/O */
setup_resource_ranges(domain, IORESOURCE_IO, &ranges); allocate_toplevel_resources(domain, IORESOURCE_IO);
allocate_child_resources(domain->link_list, &ranges, IORESOURCE_TYPE_MASK,
IORESOURCE_IO);
cleanup_domain_resource_ranges(domain, &ranges, IORESOURCE_IO);
/* /*
* Resource type Mem: * Resource type Mem:
@ -494,11 +494,9 @@ static void allocate_domain_resources(const struct device *domain)
* together when finding the best fit based on the biggest resource * together when finding the best fit based on the biggest resource
* requirement. * requirement.
*/ */
setup_resource_ranges(domain, IORESOURCE_MEM, &ranges); allocate_toplevel_resources(domain, IORESOURCE_MEM);
allocate_child_resources(domain->link_list, &ranges,
IORESOURCE_TYPE_MASK, IORESOURCE_MEM);
cleanup_domain_resource_ranges(domain, &ranges, IORESOURCE_MEM);
struct device *child;
for (child = domain->link_list->children; child; child = child->sibling) { for (child = domain->link_list->children; child; child = child->sibling) {
if (!dev_has_children(child)) if (!dev_has_children(child))
continue; continue;
@ -537,12 +535,12 @@ static void allocate_domain_resources(const struct device *domain)
* resource allocation for all immediate downstream devices is complete * resource allocation for all immediate downstream devices is complete
* at the domain level, the resource allocator walks down the subtree * at the domain level, the resource allocator walks down the subtree
* for each downstream bridge to continue the allocation process at the * for each downstream bridge to continue the allocation process at the
* bridge level. Since bridges have separate windows for i/o, mem and * bridge level. Since bridges have either their whole window allocated
* prefmem, best fit algorithm at bridge level looks for the biggest * or nothing, we only need to place downstream resources inside these
* requirement considering prefmem resources separately from non-prefmem * windows by re-using offsets that were pre-calculated in pass 1. This
* resources. This continues until resource allocation is performed for * continues until resource allocation is realized for all downstream
* all downstream bridges in the domain sub-tree. This is referred to as * bridges in the domain sub-tree. This is referred to as pass 2 of the
* pass 2 of the resource allocator. * resource allocator.
* *
* Some rules that are followed by the resource allocator: * Some rules that are followed by the resource allocator:
* - Allocate resource locations for every device as long as * - Allocate resource locations for every device as long as
@ -566,8 +564,8 @@ void allocate_resources(const struct device *root)
post_log_path(child); post_log_path(child);
/* Pass 1 - Gather requirements. */ /* Pass 1 - Relative placement. */
printk(BIOS_INFO, "=== Resource allocator: %s - Pass 1 (gathering requirements) ===\n", printk(BIOS_INFO, "=== Resource allocator: %s - Pass 1 (relative placement) ===\n",
dev_path(child)); dev_path(child));
compute_domain_resources(child); compute_domain_resources(child);