Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

regmap: rbtree: Avoid overlapping nodes

When searching for a suitable node that should be used for inserting a new
register, which does not fall within the range of any existing node, we not
only looks for nodes which are directly adjacent to the new register, but
for nodes within a certain proximity. This is done to avoid creating lots
of small nodes with just a few registers spacing in between, which would
increase memory usage as well as tree traversal time.

This means there might be multiple node candidates which fall within the
proximity range of the new register. If we choose the first node we
encounter, under certain register insertion patterns it is possible to end
up with overlapping ranges. This will break order in the rbtree and can
cause the cached register value to become corrupted.

E.g. take the simplified example where the proximity range is 2 and the
register insertion sequence is 1, 4, 2, 3, 5.
* Insert of register 1 creates a new node, this is the root of the rbtree
* Insert of register 4 creates a new node, which is inserted to the right
of the root.
* Insert of register 2 gets inserted to the first node
* Insert of register 3 gets inserted to the first node
* Insert of register 5 also gets inserted into the first node since
this is the first node encountered and it is within the proximity range.
Now there are two overlapping nodes.

To avoid this always choose the node that is closest to the new register.
This will ensure that nodes will not overlap. The tree traversal is still
done as a binary search, we just don't stop at the first node found. So the
complexity of the algorithm stays within the same order.

Ideally if a new register is in the range of two adjacent blocks those
blocks should be merged, but that is a much more invasive change and left
for later.

The issue was initially introduced in commit 472fdec7380c ("regmap: rbtree:
Reduce number of nodes, take 2"), but became much more exposed by commit
6399aea629b0 ("regmap: rbtree: When adding a reg do a bsearch for target
node") which changed the order in which nodes are looked-up.

Fixes: 6399aea629b0 ("regmap: rbtree: When adding a reg do a bsearch for target node")
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Signed-off-by: Mark Brown <broonie@kernel.org>

authored by

Lars-Peter Clausen and committed by
Mark Brown
1bc8da4e efeb1a3a

+28 -10
+28 -10
drivers/base/regmap/regcache-rbtree.c
··· 404 404 unsigned int new_base_reg, new_top_reg; 405 405 unsigned int min, max; 406 406 unsigned int max_dist; 407 + unsigned int dist, best_dist = UINT_MAX; 407 408 408 409 max_dist = map->reg_stride * sizeof(*rbnode_tmp) / 409 410 map->cache_word_size; ··· 424 423 &base_reg, &top_reg); 425 424 426 425 if (base_reg <= max && top_reg >= min) { 427 - new_base_reg = min(reg, base_reg); 428 - new_top_reg = max(reg, top_reg); 429 - } else { 430 - if (max < base_reg) 431 - node = node->rb_left; 426 + if (reg < base_reg) 427 + dist = base_reg - reg; 428 + else if (reg > top_reg) 429 + dist = reg - top_reg; 432 430 else 433 - node = node->rb_right; 434 - 435 - continue; 431 + dist = 0; 432 + if (dist < best_dist) { 433 + rbnode = rbnode_tmp; 434 + best_dist = dist; 435 + new_base_reg = min(reg, base_reg); 436 + new_top_reg = max(reg, top_reg); 437 + } 436 438 } 437 439 438 - ret = regcache_rbtree_insert_to_block(map, rbnode_tmp, 440 + /* 441 + * Keep looking, we want to choose the closest block, 442 + * otherwise we might end up creating overlapping 443 + * blocks, which breaks the rbtree. 444 + */ 445 + if (reg < base_reg) 446 + node = node->rb_left; 447 + else if (reg > top_reg) 448 + node = node->rb_right; 449 + else 450 + break; 451 + } 452 + 453 + if (rbnode) { 454 + ret = regcache_rbtree_insert_to_block(map, rbnode, 439 455 new_base_reg, 440 456 new_top_reg, reg, 441 457 value); 442 458 if (ret) 443 459 return ret; 444 - rbtree_ctx->cached_rbnode = rbnode_tmp; 460 + rbtree_ctx->cached_rbnode = rbnode; 445 461 return 0; 446 462 } 447 463