diff options
author | Nico Huber <nico.h@gmx.de> | 2020-05-23 23:20:13 +0200 |
---|---|---|
committer | Arthur Heymans <arthur@aheymans.xyz> | 2023-06-22 19:07:26 +0000 |
commit | 9260ea60bfa487851e6afda59d83214a0c967208 (patch) | |
tree | 7bd1c6769b205bc8d150a7aeaca721bd1e204f73 /src/device | |
parent | 5226301765ded70e0ef640e5252bbaca8cd14451 (diff) |
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>
Diffstat (limited to 'src/device')
-rw-r--r-- | src/device/resource_allocator_v4.c | 248 |
1 files changed, 123 insertions, 125 deletions
diff --git a/src/device/resource_allocator_v4.c b/src/device/resource_allocator_v4.c index d758f0105d..9bf5aafd23 100644 --- a/src/device/resource_allocator_v4.c +++ b/src/device/resource_allocator_v4.c @@ -49,6 +49,11 @@ static resource_t effective_limit(const struct resource *const res) * resource. This is required to guarantee that the upstream bridge/ * domain honors the limit and alignment requirements for this bridge * 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, 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 * condition can only be true for the first (largest) resource. For all - * other children resources, alignment is taken care of by updating the - * base to round up as per the child resource alignment. It is - * 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. + * other child resources, alignment is taken care of by rounding their + * base up. */ if (child_res->align > bridge_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 * device has stricter requirements w.r.t. limits for any resource, that - * constraint needs to be propagated back up to the downstream bridges - * of the domain. This guarantees that the resource allocation which - * starts at the domain level takes into account all these constraints - * thus working on a global view. + * constraint needs to be propagated back up to the bridges downstream + * of the domain. This way, the whole bridge resource fulfills the limit. */ if (effective_limit(child_res) < bridge_res->limit) 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)); + /* + * 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", dev_path(child), child_res->index, base, base + child_res->size - 1, 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, 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 - * resource that is provided. If the given resource is unassigned or if - * the resource window size is 0, then it creates an empty list. This - * results in resource allocation for that resource type failing for - * all downstream devices since there is nothing to allocate from. - * - * In case of domain, it applies additional constraints to ensure that - * 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. + * resource that is provided. It applies additional constraints to + * ensure that the memranges do not overlap any of the fixed resources + * under the domain. The domain typically provides a memrange for the + * 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, - struct memranges *ranges) +static void setup_resource_ranges(const struct device *const domain, + const unsigned long type, + struct memranges *const ranges) { - 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 long type_mask = IORESOURCE_TYPE_MASK | IORESOURCE_FIXED; const unsigned char alignment = get_alignment_by_resource_type(type); memranges_init_empty_with_alignment(ranges, NULL, 0, alignment); - for (struct resource *res = dev->resource_list; res != NULL; res = res->next) { - if ((res->flags & type_mask) != type_match) + for (struct resource *res = domain->resource_list; res != NULL; res = res->next) { + if ((res->flags & type_mask) != type) continue; 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); 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(dev, ranges, type); + constrain_domain_resources(domain, 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) @@ -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 - * all the resources for the bridge and generates a list of memory - * ranges similar to that at the domain level. However, there is no need - * to apply any additional constraints since the window allocated to the - * bridge is guaranteed to be non-overlapping by the allocator at domain - * level. - * - * 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. + * all the resources for the bridge and assigns all the base addresses + * of its children's resources of the same type. update_bridge_resource() + * of pass 1 pre-calculated the offsets of these bases inside the bridge + * resource. Now that the bridge resource is allocated, all we have to + * do is to add its final base to these offsets. * * Once allocation at the current bridge is complete, resource allocator * continues walking down the downstream bridges until it hits the leaf * 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) { - struct memranges ranges; - const struct resource *res; - struct bus *bus = bridge->link_list; - unsigned long type_match; + const unsigned long type_mask = + IORESOURCE_TYPE_MASK | IORESOURCE_PREFETCH | IORESOURCE_FIXED; + struct bus *const bus = bridge->link_list; + struct resource *res; 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) continue; if (!(res->flags & IORESOURCE_BRIDGE)) continue; - type_match = res->flags & type_mask; + if (!(res->flags & IORESOURCE_ASSIGNED)) + continue; - setup_resource_ranges(bridge, type_match, &ranges); - allocate_child_resources(bus, &ranges, type_mask, type_match); - print_resource_done(bridge, res); - memranges_teardown(&ranges); + /* Run assign_resource_cb() for all downstream resources of the same type. */ + search_bus_resources(bus, type_mask, res->flags & type_mask, + assign_resource_cb, &res->base); } - for (child = bus->children; child; child = child->sibling) { + for (child = bus->children; child != NULL; child = child->sibling) { if (!dev_has_children(child)) continue; @@ -473,19 +479,13 @@ static void allocate_bridge_resources(const struct device *bridge) * resource requirements of the downstream devices. * * Once resources are allocated to all downstream devices of the domain, - * it walks down each downstream bridge to continue the same process - * until resources are allocated to all devices under the domain. + * it walks down each downstream bridge to finish resource assignment + * of its children resources within its own window. */ static void allocate_domain_resources(const struct device *domain) { - struct memranges ranges; - struct device *child; - /* Resource type I/O */ - setup_resource_ranges(domain, IORESOURCE_IO, &ranges); - allocate_child_resources(domain->link_list, &ranges, IORESOURCE_TYPE_MASK, - IORESOURCE_IO); - cleanup_domain_resource_ranges(domain, &ranges, IORESOURCE_IO); + allocate_toplevel_resources(domain, IORESOURCE_IO); /* * 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 * requirement. */ - setup_resource_ranges(domain, IORESOURCE_MEM, &ranges); - allocate_child_resources(domain->link_list, &ranges, - IORESOURCE_TYPE_MASK, IORESOURCE_MEM); - cleanup_domain_resource_ranges(domain, &ranges, IORESOURCE_MEM); + allocate_toplevel_resources(domain, IORESOURCE_MEM); + struct device *child; for (child = domain->link_list->children; child; child = child->sibling) { if (!dev_has_children(child)) continue; @@ -537,12 +535,12 @@ static void allocate_domain_resources(const struct device *domain) * resource allocation for all immediate downstream devices is complete * at the domain level, the resource allocator walks down the subtree * for each downstream bridge to continue the allocation process at the - * bridge level. Since bridges have separate windows for i/o, mem and - * prefmem, best fit algorithm at bridge level looks for the biggest - * requirement considering prefmem resources separately from non-prefmem - * resources. This continues until resource allocation is performed for - * all downstream bridges in the domain sub-tree. This is referred to as - * pass 2 of the resource allocator. + * bridge level. Since bridges have either their whole window allocated + * or nothing, we only need to place downstream resources inside these + * windows by re-using offsets that were pre-calculated in pass 1. This + * continues until resource allocation is realized for all downstream + * bridges in the domain sub-tree. This is referred to as pass 2 of the + * resource allocator. * * Some rules that are followed by the resource allocator: * - Allocate resource locations for every device as long as @@ -566,8 +564,8 @@ void allocate_resources(const struct device *root) post_log_path(child); - /* Pass 1 - Gather requirements. */ - printk(BIOS_INFO, "=== Resource allocator: %s - Pass 1 (gathering requirements) ===\n", + /* Pass 1 - Relative placement. */ + printk(BIOS_INFO, "=== Resource allocator: %s - Pass 1 (relative placement) ===\n", dev_path(child)); compute_domain_resources(child); |