summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNico Huber <nico.h@gmx.de>2020-05-23 19:15:36 +0200
committerArthur Heymans <arthur@aheymans.xyz>2023-06-22 19:07:18 +0000
commit5226301765ded70e0ef640e5252bbaca8cd14451 (patch)
treeb8d3c842467b7d19c5b9d4140391e7c32c4989dd
parentd7a354dab0fb45ff2161a535003dadc7cdb59f95 (diff)
allocator_v4: Treat above 4G resources more natively
We currently have two competing mechanisms to limit the placement of resources: 1. the explicit `.limit` field of a resource, and 2. the IORESOURCE_ABOVE_4G flag. This makes the resource allocator unnecessarily complex. Ideally, we would always reduce the `.limit` field if we want to "pin" a specific resource below 4G. However, as that's not done across the tree yet, we will use the _absence_ of the IORESOURCE_ABOVE_4G flag as a hint to implicitly lower the `limit` of a resource. In this patch, this is done inside the effective_limit() function that hides the flag from the rest of the allocator. To automatically place resources above 4G if their limit allows it, we have to allocate from top down. Hence, we disable the prompt for RESOURCE_ALLOCATION_TOP_DOWN and turn it on by default. Platforms that are incompatible should be fixed, but can also override the default as a temporary measure. One implication of the changes is that we act differently when a cold-plugged device reports a prefetchable resource with 32-bit limit. Before this change, we would fail to allocate the resource. After this change, it forces everything on the same root port below the 4G line. A possible solution to get completely rid of the IORESOURCE_ABOVE_4G flag would be rules to place resources of certain devices below 4G. For instance, the primary VGA device and storage and HID devices could be made available to a payload that can only address 32 bits. For now, effective_limit() provides us enough abstraction as if the `limit` would be the only variable to consider. With this, we get rid of all the special handling of above 4G resources during phase 2 of the allocator. Which saves us about 20% of the code :D An earlier version of this change (commit 117e43611548) had to be reverted because of missing resource reservations in platform code. This is worked around now with commit ae81497cb6c7 (device/pci: Limit default domain memory window). Change-Id: Ia822f0ce648c7f7afc801d9cb00b6459fe7cebea Signed-off-by: Nico Huber <nico.h@gmx.de> Original-reviewed-on: https://review.coreboot.org/c/coreboot/+/65413 Original-reviewed-by: Arthur Heymans <arthur@aheymans.xyz> Original-reviewed-by: Tim Wawrzynczak <twawrzynczak@chromium.org> Reviewed-on: https://review.coreboot.org/c/coreboot/+/75012 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Felix Singer <service+coreboot-gerrit@felixsinger.de> Reviewed-by: Lean Sheng Tan <sheng.tan@9elements.com> Reviewed-by: Arthur Heymans <arthur@aheymans.xyz>
-rw-r--r--src/device/Kconfig9
-rw-r--r--src/device/resource_allocator_v4.c231
2 files changed, 51 insertions, 189 deletions
diff --git a/src/device/Kconfig b/src/device/Kconfig
index 9c9ecd1973..2d2c6fffa7 100644
--- a/src/device/Kconfig
+++ b/src/device/Kconfig
@@ -994,11 +994,12 @@ config I2C_TRANSFER_TIMEOUT_US
is aborted and an error returned.
config RESOURCE_ALLOCATION_TOP_DOWN
- bool "Allocate resources from top down"
- default n
+ def_bool y
help
- Should be the default, but many platforms don't report resources
- correctly. Hence, the allocator might cause conflicts.
+ Top-down allocation is required to place resources above 4G by
+ default (i.e. even when there is still space below). On some
+ platforms, it might make a difference because of conflicts with
+ undeclared resources.
config XHCI_UTILS
def_bool n
diff --git a/src/device/resource_allocator_v4.c b/src/device/resource_allocator_v4.c
index b6b9fb9c93..d758f0105d 100644
--- a/src/device/resource_allocator_v4.c
+++ b/src/device/resource_allocator_v4.c
@@ -24,6 +24,17 @@ static bool dev_has_children(const struct device *dev)
return bus && bus->children;
}
+static resource_t effective_limit(const struct resource *const res)
+{
+ /* Always allow bridge resources above 4G. */
+ if (res->flags & IORESOURCE_BRIDGE)
+ return res->limit;
+
+ const resource_t quirk_4g_limit =
+ res->flags & IORESOURCE_ABOVE_4G ? UINT64_MAX : UINT32_MAX;
+ return MIN(res->limit, quirk_4g_limit);
+}
+
#define res_printk(depth, str, ...) printk(BIOS_DEBUG, "%*c"str, depth, ' ', __VA_ARGS__)
/*
@@ -97,22 +108,8 @@ static void update_bridge_resource(const struct device *bridge, struct resource
* starts at the domain level takes into account all these constraints
* thus working on a global view.
*/
- if (child_res->limit < bridge_res->limit)
- bridge_res->limit = child_res->limit;
-
- /*
- * Propagate the downstream resource request to allocate above 4G
- * boundary to upstream bridge resource. This ensures that during
- * pass 2, the resource allocator at domain level has a global view
- * of all the downstream device requirements and thus address space
- * is allocated as per updated flags in the bridge resource.
- *
- * Since the bridge resource is a single window, all the downstream
- * resources of this bridge resource will be allocated in space above
- * the 4G boundary.
- */
- if (child_res->flags & IORESOURCE_ABOVE_4G)
- bridge_res->flags |= IORESOURCE_ABOVE_4G;
+ if (effective_limit(child_res) < bridge_res->limit)
+ bridge_res->limit = effective_limit(child_res);
/*
* Alignment value of 0 means that the child resource has no alignment
@@ -225,147 +222,6 @@ static unsigned char get_alignment_by_resource_type(const unsigned long type)
die("Unexpected resource type: flags(%lu)!\n", type);
}
-/*
- * If the resource is NULL or if the resource is not assigned, then it
- * cannot be used for allocation for downstream devices.
- */
-static bool is_resource_invalid(const struct resource *res)
-{
- return (res == NULL) || !(res->flags & IORESOURCE_ASSIGNED);
-}
-
-static void initialize_domain_io_resource_memranges(struct memranges *ranges,
- const struct resource *res,
- unsigned long memrange_type)
-{
- memranges_insert(ranges, res->base, res->limit - res->base + 1, memrange_type);
-}
-
-static void initialize_domain_mem_resource_memranges(struct memranges *ranges,
- const struct resource *res,
- unsigned long memrange_type)
-{
- resource_t res_base;
- resource_t res_limit;
-
- const resource_t limit_4g = 0xffffffff;
-
- res_base = res->base;
- res_limit = res->limit;
-
- /*
- * Split the resource into two separate ranges if it crosses the 4G
- * boundary. Memrange type is set differently to ensure that memrange
- * does not merge these two ranges. For the range above 4G boundary,
- * given memrange type is ORed with IORESOURCE_ABOVE_4G.
- */
- if (res_base <= limit_4g) {
-
- resource_t range_limit;
-
- /* Clip the resource limit at 4G boundary if necessary. */
- range_limit = MIN(res_limit, limit_4g);
- memranges_insert(ranges, res_base, range_limit - res_base + 1, memrange_type);
-
- /*
- * If the resource lies completely below the 4G boundary, nothing more
- * needs to be done.
- */
- if (res_limit <= limit_4g)
- return;
-
- /*
- * If the resource window crosses the 4G boundary, then update res_base
- * to add another entry for the range above the boundary.
- */
- res_base = limit_4g + 1;
- }
-
- if (res_base > res_limit)
- return;
-
- /*
- * If resource lies completely above the 4G boundary or if the resource
- * was clipped to add two separate ranges, the range above 4G boundary
- * has the resource flag IORESOURCE_ABOVE_4G set. This allows domain to
- * handle any downstream requests for resource allocation above 4G
- * differently.
- */
- memranges_insert(ranges, res_base, res_limit - res_base + 1,
- memrange_type | IORESOURCE_ABOVE_4G);
-}
-
-/*
- * This function initializes memranges for domain device. If the
- * resource crosses 4G boundary, then this function splits it into two
- * ranges -- one for the window below 4G and the other for the window
- * above 4G. The latter range has IORESOURCE_ABOVE_4G flag set to
- * satisfy resource requests from downstream devices for allocations
- * above 4G.
- */
-static void initialize_domain_memranges(const struct device *dev, struct memranges *ranges,
- unsigned long memrange_type)
-{
- unsigned char align = get_alignment_by_resource_type(memrange_type);
-
- memranges_init_empty_with_alignment(ranges, NULL, 0, align);
-
- struct resource *res;
- for (res = dev->resource_list; res != NULL; res = res->next) {
- if (is_resource_invalid(res))
- continue;
- if (res->flags & IORESOURCE_FIXED)
- continue;
- if ((res->flags & IORESOURCE_TYPE_MASK) != memrange_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,
- res->gran, res->limit);
-
- if (res->flags & IORESOURCE_IO)
- initialize_domain_io_resource_memranges(ranges, res, memrange_type);
- else
- initialize_domain_mem_resource_memranges(ranges, res, memrange_type);
- }
-}
-
-/*
- * This function initializes memranges for bridge device. Unlike domain,
- * bridge does not need to care about resource window crossing 4G
- * boundary. This is handled by the resource allocator at domain level
- * to ensure that all downstream bridges are allocated space either
- * above or below 4G boundary as per the state of IORESOURCE_ABOVE_4G
- * for the respective bridge resource.
- *
- * So, this function creates a single range of the entire resource
- * window available for the bridge resource. Thus all downstream
- * resources of the bridge for the given resource type get allocated
- * space from the same window. If there is any downstream resource of
- * the bridge which requests allocation above 4G, then all other
- * downstream resources of the same type under the bridge get allocated
- * above 4G.
- */
-static void initialize_bridge_memranges(const struct device *dev, struct memranges *ranges,
- unsigned long memrange_type)
-{
- unsigned char align = get_alignment_by_resource_type(memrange_type);
-
- memranges_init_empty_with_alignment(ranges, NULL, 0, align);
-
- struct resource *res;
- for (res = dev->resource_list; res != NULL; res = res->next) {
- if (is_resource_invalid(res))
- continue;
- if (res->flags & IORESOURCE_FIXED)
- continue;
- if ((res->flags & (IORESOURCE_TYPE_MASK | IORESOURCE_PREFETCH)) == memrange_type)
- break;
- }
-
- memranges_insert(ranges, res->base, res->limit - res->base + 1, memrange_type);
-}
-
static void print_resource_ranges(const struct device *dev, const struct memranges *ranges)
{
const struct range_entry *r;
@@ -403,12 +259,13 @@ static void allocate_child_resources(struct bus *bus, struct memranges *ranges,
if (!resource->size)
continue;
- if (memranges_steal(ranges, resource->limit, resource->size, resource->align,
- type_match, &resource->base, allocate_top_down) == false) {
+ 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, resource->limit, resource2str(resource));
+ dev_path(dev), resource->index, resource->size,
+ effective_limit(resource), resource2str(resource));
continue;
}
@@ -491,8 +348,8 @@ 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 NULL or if the
- * resource window size is 0, then it creates an empty list. This
+ * 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.
*
@@ -506,13 +363,33 @@ static void constrain_domain_resources(const struct device *domain, struct memra
static void setup_resource_ranges(const struct device *dev, unsigned long type,
struct memranges *ranges)
{
- if (dev->path.type == DEVICE_PATH_DOMAIN) {
- initialize_domain_memranges(dev, ranges, type);
- constrain_domain_resources(dev, ranges, type);
- } else {
- initialize_bridge_memranges(dev, ranges, type);
+ 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);
+
+ 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)
+ 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,
+ 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);
+
print_resource_ranges(dev, ranges);
}
@@ -528,8 +405,6 @@ static void cleanup_domain_resource_ranges(const struct device *dev, struct memr
{
memranges_teardown(ranges);
for (struct resource *res = dev->resource_list; res != NULL; res = res->next) {
- if (is_resource_invalid(res))
- continue;
if (res->flags & IORESOURCE_FIXED)
continue;
if ((res->flags & IORESOURCE_TYPE_MASK) != type)
@@ -618,24 +493,10 @@ static void allocate_domain_resources(const struct device *domain)
* the resource allocation at domain level considers mem and prefmem
* together when finding the best fit based on the biggest resource
* requirement.
- *
- * However, resource requests for allocation above 4G boundary need to
- * be handled separately if the domain resource window crosses this
- * boundary. There is a single window for resource of type
- * IORESOURCE_MEM. When creating memranges, this resource is split into
- * two separate ranges -- one for the window below 4G boundary and other
- * for the window above 4G boundary (with IORESOURCE_ABOVE_4G flag set).
- * Thus, when allocating child resources, requests for below and above
- * the 4G boundary are handled separately by setting the type_mask and
- * type_match to allocate_child_resources() accordingly.
*/
setup_resource_ranges(domain, IORESOURCE_MEM, &ranges);
allocate_child_resources(domain->link_list, &ranges,
- IORESOURCE_TYPE_MASK | IORESOURCE_ABOVE_4G,
- IORESOURCE_MEM);
- allocate_child_resources(domain->link_list, &ranges,
- IORESOURCE_TYPE_MASK | IORESOURCE_ABOVE_4G,
- IORESOURCE_MEM | IORESOURCE_ABOVE_4G);
+ IORESOURCE_TYPE_MASK, IORESOURCE_MEM);
cleanup_domain_resource_ranges(domain, &ranges, IORESOURCE_MEM);
for (child = domain->link_list->children; child; child = child->sibling) {