From bd5fb66d960a6fe9bc16fad4571110001a4fc5bf Mon Sep 17 00:00:00 2001 From: Nico Huber Date: Sat, 7 Oct 2017 13:40:19 +0200 Subject: cpu/x86/mtrr: Optimize hole carving strategy For WB ranges with unaligned end, we try to align the range up and carve a hole out of it which might reduce MTRR usage. Instead of trying an arbitrary alignment, we try all and choose an optimal one. Also, restructure the cases when we try to find a hole. Which leads us to the following three: 1. WB range is last in address space: Aligning up, up to the next power of 2, may gain us something. 2. The next range is of type UC: We may align up, up to the _end_ of the next range. If there is a gap between the current and the next range, it would have been covered by the default type UC anyway. 3. The next range is not of type UC: We may align up, up to the _base_ of the next range. This is the end of the gap, if there is one. Change-Id: Iefb064ce8c4f293490a19dd46054b966c63bde44 Signed-off-by: Nico Huber Reviewed-on: https://review.coreboot.org/21915 Tested-by: build bot (Jenkins) Reviewed-by: Aaron Durbin --- src/cpu/x86/mtrr/mtrr.c | 132 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 98 insertions(+), 34 deletions(-) (limited to 'src') diff --git a/src/cpu/x86/mtrr/mtrr.c b/src/cpu/x86/mtrr/mtrr.c index 08312fbebf..7b1b659097 100644 --- a/src/cpu/x86/mtrr/mtrr.c +++ b/src/cpu/x86/mtrr/mtrr.c @@ -129,8 +129,6 @@ static void enable_var_mtrr(unsigned char deftype) #define RANGE_TO_PHYS_ADDR(x) (((resource_t)(x)) << RANGE_SHIFT) #define NUM_FIXED_MTRRS (NUM_FIXED_RANGES / RANGES_PER_FIXED_MTRR) -/* The minimum alignment while handling variable MTRR ranges is 64MiB. */ -#define MTRR_MIN_ALIGN PHYS_TO_RANGE_ADDR(64 << 20) /* Helpful constants. */ #define RANGE_1MB PHYS_TO_RANGE_ADDR(1 << 20) #define RANGE_4GB (1 << (ADDR_SHIFT_TO_RANGE_SHIFT(32))) @@ -499,22 +497,79 @@ static void calc_var_mtrr_range(struct var_mtrr_state *var_state, } } +static uint32_t optimize_var_mtrr_hole(const uint32_t base, + const uint32_t hole, + const uint64_t limit, + const int carve_hole) +{ + /* + * With default type UC, we can potentially optimize a WB + * range with unaligned upper end, by aligning it up and + * carving the added "hole" out again. + * + * To optimize the upper end of the hole, we will test + * how many MTRRs calc_var_mtrr_range() will spend for any + * alignment of the hole's upper end. + * + * We take four parameters, the lower end of the WB range + * `base`, upper end of the WB range as start of the `hole`, + * a `limit` how far we may align the upper end of the hole + * up and a flag `carve_hole` whether we should count MTRRs + * for carving the hole out. We return the optimal upper end + * for the hole (which may be the same as the end of the WB + * range in case we don't gain anything by aligning up). + */ + + const int dont_care = 0; + struct var_mtrr_state var_state = { 0, }; + + unsigned int align, best_count; + uint32_t best_end = hole; + + /* calculate MTRR count for the WB range alone (w/o a hole) */ + calc_var_mtrr_range(&var_state, base, hole - base, dont_care); + best_count = var_state.mtrr_index; + var_state.mtrr_index = 0; + + for (align = fls(hole) + 1; align <= fms(hole); ++align) { + const uint64_t hole_end = ALIGN_UP((uint64_t)hole, 1 << align); + if (hole_end > limit) + break; + + /* calculate MTRR count for this alignment */ + calc_var_mtrr_range( + &var_state, base, hole_end - base, dont_care); + if (carve_hole) + calc_var_mtrr_range( + &var_state, hole, hole_end - hole, dont_care); + + if (var_state.mtrr_index < best_count) { + best_count = var_state.mtrr_index; + best_end = hole_end; + } + var_state.mtrr_index = 0; + } + + return best_end; +} + static void calc_var_mtrrs_with_hole(struct var_mtrr_state *var_state, struct range_entry *r) { uint32_t a1, a2, b1, b2; - int mtrr_type; + uint64_t b2_limit; + int mtrr_type, carve_hole; struct range_entry *next; /* * Determine MTRRs based on the following algorithm for the given entry: * +------------------+ b2 = ALIGN_UP(end) * | 0 or more bytes | <-- hole is carved out between b1 and b2 - * +------------------+ a2 = b1 = end + * +------------------+ a2 = b1 = original end * | | * +------------------+ a1 = begin * - * Thus, there are 3 sub-ranges to configure variable MTRRs for. + * Thus, there are up to 2 sub-ranges to configure variable MTRRs for. */ mtrr_type = range_entry_mtrr_type(r); @@ -541,40 +596,49 @@ static void calc_var_mtrrs_with_hole(struct var_mtrr_state *var_state, if (!var_state->above4gb && a2 > RANGE_4GB) a2 = RANGE_4GB; - next = memranges_next_entry(var_state->addr_space, r); - b1 = a2; - /* First check if a1 is >= 4GiB and the current entry is the last - * entry. If so perform an optimization of covering a larger range - * defined by the base address' alignment. */ - if (a1 >= RANGE_4GB && next == NULL) { - uint32_t addr_lsb; - - addr_lsb = fls(a1); - b2 = (1 << addr_lsb) + a1; - if (b2 >= a2) { - calc_var_mtrr_range(var_state, a1, b2 - a1, mtrr_type); - return; - } - } - - /* Handle the min alignment roundup case. */ - b2 = ALIGN_UP(a2, MTRR_MIN_ALIGN); - - /* Check against the next range. If the current range_entry is the - * last entry then carving a hole is no problem. If the current entry - * isn't the last entry then check that the last entry covers the - * entire hole range with the default mtrr type. */ - if (next != NULL && - (range_entry_mtrr_type(next) != var_state->def_mtrr_type || - range_entry_end_mtrr_addr(next) < b2)) { - calc_var_mtrr_range(var_state, a1, a2 - a1, mtrr_type); - return; + /* + * Depending on the type of the next range, there are three + * different situations to handle: + * + * 1. WB range is last in address space: + * Aligning up, up to the next power of 2, may gain us + * something. + * + * 2. The next range is of type UC: + * We may align up, up to the _end_ of the next range. If + * there is a gap between the current and the next range, + * it would have been covered by the default type UC anyway. + * + * 3. The next range is not of type UC: + * We may align up, up to the _base_ of the next range. This + * may either be the end of the current range (if the next + * range follows immediately) or the end of the gap between + * the ranges. + */ + next = memranges_next_entry(var_state->addr_space, r); + if (next == NULL) { + b2_limit = ALIGN_UP((uint64_t)b1, 1 << fms(b1)); + /* If it's the last range above 4GiB, we won't carve + the hole out. If an OS wanted to move MMIO there, + it would have to override the MTRR setting using + PAT just like it would with WB as default type. */ + carve_hole = a1 < RANGE_4GB; + } else if (range_entry_mtrr_type(next) == MTRR_TYPE_UNCACHEABLE) { + b2_limit = range_entry_end_mtrr_addr(next); + carve_hole = 1; + } else { + b2_limit = range_entry_base_mtrr_addr(next); + carve_hole = 1; } + b2 = optimize_var_mtrr_hole(a1, b1, b2_limit, carve_hole); calc_var_mtrr_range(var_state, a1, b2 - a1, mtrr_type); - calc_var_mtrr_range(var_state, b1, b2 - b1, var_state->def_mtrr_type); + if (carve_hole && b2 != b1) { + calc_var_mtrr_range(var_state, b1, b2 - b1, + MTRR_TYPE_UNCACHEABLE); + } } static void calc_var_mtrrs_without_hole(struct var_mtrr_state *var_state, -- cgit v1.2.3