Skip to content

Commit

Permalink
regmap: Ensure range selector registers are updated after cache sync
Browse files Browse the repository at this point in the history
commit 0ec7731 upstream.

When we sync the register cache we do so with the cache bypassed in order
to avoid overhead from writing the synced values back into the cache. If
the regmap has ranges and the selector register for those ranges is in a
register which is cached this has the unfortunate side effect of meaning
that the physical and cached copies of the selector register can be out of
sync after a cache sync. The cache will have whatever the selector was when
the sync started and the hardware will have the selector for the register
that was synced last.

Fix this by rewriting all cached selector registers after every sync,
ensuring that the hardware and cache have the same content. This will
result in extra writes that wouldn't otherwise be needed but is simple
so hopefully robust. We don't read from the hardware since not all
devices have physical read support.

Given that nobody noticed this until now it is likely that we are rarely if
ever hitting this case.

Reported-by: Hector Martin <marcan@marcan.st>
Cc: stable@vger.kernel.org
Signed-off-by: Mark Brown <broonie@kernel.org>
Link: https://lore.kernel.org/r/20231026-regmap-fix-selector-sync-v1-1-633ded82770d@kernel.org
Signed-off-by: Mark Brown <broonie@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
  • Loading branch information
broonie authored and gregkh committed Nov 28, 2023
1 parent e10facb commit 164fa9a
Showing 1 changed file with 30 additions and 0 deletions.
30 changes: 30 additions & 0 deletions drivers/base/regmap/regcache.c
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,11 @@ static int regcache_default_sync(struct regmap *map, unsigned int min,
return 0;
}

static int rbtree_all(const void *key, const struct rb_node *node)
{
return 0;
}

/**
* regcache_sync - Sync the register cache with the hardware.
*
Expand All @@ -348,6 +353,7 @@ int regcache_sync(struct regmap *map)
unsigned int i;
const char *name;
bool bypass;
struct rb_node *node;

if (WARN_ON(map->cache_type == REGCACHE_NONE))
return -EINVAL;
Expand Down Expand Up @@ -392,6 +398,30 @@ int regcache_sync(struct regmap *map)
map->async = false;
map->cache_bypass = bypass;
map->no_sync_defaults = false;

/*
* If we did any paging with cache bypassed and a cached
* paging register then the register and cache state might
* have gone out of sync, force writes of all the paging
* registers.
*/
rb_for_each(node, 0, &map->range_tree, rbtree_all) {
struct regmap_range_node *this =
rb_entry(node, struct regmap_range_node, node);

/* If there's nothing in the cache there's nothing to sync */
ret = regcache_read(map, this->selector_reg, &i);
if (ret != 0)
continue;

ret = _regmap_write(map, this->selector_reg, i);
if (ret != 0) {
dev_err(map->dev, "Failed to write %x = %x: %d\n",
this->selector_reg, i, ret);
break;
}
}

map->unlock(map->lock_arg);

regmap_async_complete(map);
Expand Down

0 comments on commit 164fa9a

Please sign in to comment.