aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLars-Peter Clausen <lars@metafoo.de>2016-08-04 11:22:16 -0400
committerMark Brown <broonie@kernel.org>2016-08-04 11:55:26 -0400
commit1bc8da4e143c0fd8807e061a66d91d5972601ab1 (patch)
tree861baf325f6eed38c3fc496af2ce03cc962c7694
parentefeb1a3ab91dc6bead3b5a522c0ceca1d711feb1 (diff)
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>
-rw-r--r--drivers/base/regmap/regcache-rbtree.c38
1 files changed, 28 insertions, 10 deletions
diff --git a/drivers/base/regmap/regcache-rbtree.c b/drivers/base/regmap/regcache-rbtree.c
index aa56af87d941..b11af3f2c1db 100644
--- a/drivers/base/regmap/regcache-rbtree.c
+++ b/drivers/base/regmap/regcache-rbtree.c
@@ -404,6 +404,7 @@ static int regcache_rbtree_write(struct regmap *map, unsigned int reg,
404 unsigned int new_base_reg, new_top_reg; 404 unsigned int new_base_reg, new_top_reg;
405 unsigned int min, max; 405 unsigned int min, max;
406 unsigned int max_dist; 406 unsigned int max_dist;
407 unsigned int dist, best_dist = UINT_MAX;
407 408
408 max_dist = map->reg_stride * sizeof(*rbnode_tmp) / 409 max_dist = map->reg_stride * sizeof(*rbnode_tmp) /
409 map->cache_word_size; 410 map->cache_word_size;
@@ -423,24 +424,41 @@ static int regcache_rbtree_write(struct regmap *map, unsigned int reg,
423 &base_reg, &top_reg); 424 &base_reg, &top_reg);
424 425
425 if (base_reg <= max && top_reg >= min) { 426 if (base_reg <= max && top_reg >= min) {
426 new_base_reg = min(reg, base_reg); 427 if (reg < base_reg)
427 new_top_reg = max(reg, top_reg); 428 dist = base_reg - reg;
428 } else { 429 else if (reg > top_reg)
429 if (max < base_reg) 430 dist = reg - top_reg;
430 node = node->rb_left;
431 else 431 else
432 node = node->rb_right; 432 dist = 0;
433 433 if (dist < best_dist) {
434 continue; 434 rbnode = rbnode_tmp;
435 best_dist = dist;
436 new_base_reg = min(reg, base_reg);
437 new_top_reg = max(reg, top_reg);
438 }
435 } 439 }
436 440
437 ret = regcache_rbtree_insert_to_block(map, rbnode_tmp, 441 /*
442 * Keep looking, we want to choose the closest block,
443 * otherwise we might end up creating overlapping
444 * blocks, which breaks the rbtree.
445 */
446 if (reg < base_reg)
447 node = node->rb_left;
448 else if (reg > top_reg)
449 node = node->rb_right;
450 else
451 break;
452 }
453
454 if (rbnode) {
455 ret = regcache_rbtree_insert_to_block(map, rbnode,
438 new_base_reg, 456 new_base_reg,
439 new_top_reg, reg, 457 new_top_reg, reg,
440 value); 458 value);
441 if (ret) 459 if (ret)
442 return ret; 460 return ret;
443 rbtree_ctx->cached_rbnode = rbnode_tmp; 461 rbtree_ctx->cached_rbnode = rbnode;
444 return 0; 462 return 0;
445 } 463 }
446 464