mirror of
https://github.com/torvalds/linux.git
synced 2024-12-28 05:41:55 +00:00
8a8317f927
Merge series from Richard Fitzgerald <rf@opensource.cirrus.com>: This series adds some more test cases, mainly for testing: commiteaa03486d9
("regmap: maple: Fix uninitialized symbol 'ret' warnings") commit00bb549d7d
("regmap: maple: Fix cache corruption in regcache_maple_drop()") And the pending patch ("regmap: Add regmap_read_bypassed()") There are also a few small improvements to the KUnit implementation.
399 lines
7.7 KiB
C
399 lines
7.7 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
//
|
|
// Register cache access API - maple tree based cache
|
|
//
|
|
// Copyright 2023 Arm, Ltd
|
|
//
|
|
// Author: Mark Brown <broonie@kernel.org>
|
|
|
|
#include <linux/debugfs.h>
|
|
#include <linux/device.h>
|
|
#include <linux/maple_tree.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include "internal.h"
|
|
|
|
static int regcache_maple_read(struct regmap *map,
|
|
unsigned int reg, unsigned int *value)
|
|
{
|
|
struct maple_tree *mt = map->cache;
|
|
MA_STATE(mas, mt, reg, reg);
|
|
unsigned long *entry;
|
|
|
|
rcu_read_lock();
|
|
|
|
entry = mas_walk(&mas);
|
|
if (!entry) {
|
|
rcu_read_unlock();
|
|
return -ENOENT;
|
|
}
|
|
|
|
*value = entry[reg - mas.index];
|
|
|
|
rcu_read_unlock();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int regcache_maple_write(struct regmap *map, unsigned int reg,
|
|
unsigned int val)
|
|
{
|
|
struct maple_tree *mt = map->cache;
|
|
MA_STATE(mas, mt, reg, reg);
|
|
unsigned long *entry, *upper, *lower;
|
|
unsigned long index, last;
|
|
size_t lower_sz, upper_sz;
|
|
int ret;
|
|
|
|
rcu_read_lock();
|
|
|
|
entry = mas_walk(&mas);
|
|
if (entry) {
|
|
entry[reg - mas.index] = val;
|
|
rcu_read_unlock();
|
|
return 0;
|
|
}
|
|
|
|
/* Any adjacent entries to extend/merge? */
|
|
mas_set_range(&mas, reg - 1, reg + 1);
|
|
index = reg;
|
|
last = reg;
|
|
|
|
lower = mas_find(&mas, reg - 1);
|
|
if (lower) {
|
|
index = mas.index;
|
|
lower_sz = (mas.last - mas.index + 1) * sizeof(unsigned long);
|
|
}
|
|
|
|
upper = mas_find(&mas, reg + 1);
|
|
if (upper) {
|
|
last = mas.last;
|
|
upper_sz = (mas.last - mas.index + 1) * sizeof(unsigned long);
|
|
}
|
|
|
|
rcu_read_unlock();
|
|
|
|
entry = kmalloc((last - index + 1) * sizeof(unsigned long),
|
|
map->alloc_flags);
|
|
if (!entry)
|
|
return -ENOMEM;
|
|
|
|
if (lower)
|
|
memcpy(entry, lower, lower_sz);
|
|
entry[reg - index] = val;
|
|
if (upper)
|
|
memcpy(&entry[reg - index + 1], upper, upper_sz);
|
|
|
|
/*
|
|
* This is safe because the regmap lock means the Maple lock
|
|
* is redundant, but we need to take it due to lockdep asserts
|
|
* in the maple tree code.
|
|
*/
|
|
mas_lock(&mas);
|
|
|
|
mas_set_range(&mas, index, last);
|
|
ret = mas_store_gfp(&mas, entry, map->alloc_flags);
|
|
|
|
mas_unlock(&mas);
|
|
|
|
if (ret == 0) {
|
|
kfree(lower);
|
|
kfree(upper);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int regcache_maple_drop(struct regmap *map, unsigned int min,
|
|
unsigned int max)
|
|
{
|
|
struct maple_tree *mt = map->cache;
|
|
MA_STATE(mas, mt, min, max);
|
|
unsigned long *entry, *lower, *upper;
|
|
unsigned long lower_index, lower_last;
|
|
unsigned long upper_index, upper_last;
|
|
int ret = 0;
|
|
|
|
lower = NULL;
|
|
upper = NULL;
|
|
|
|
mas_lock(&mas);
|
|
|
|
mas_for_each(&mas, entry, max) {
|
|
/*
|
|
* This is safe because the regmap lock means the
|
|
* Maple lock is redundant, but we need to take it due
|
|
* to lockdep asserts in the maple tree code.
|
|
*/
|
|
mas_unlock(&mas);
|
|
|
|
/* Do we need to save any of this entry? */
|
|
if (mas.index < min) {
|
|
lower_index = mas.index;
|
|
lower_last = min -1;
|
|
|
|
lower = kmemdup(entry, ((min - mas.index) *
|
|
sizeof(unsigned long)),
|
|
map->alloc_flags);
|
|
if (!lower) {
|
|
ret = -ENOMEM;
|
|
goto out_unlocked;
|
|
}
|
|
}
|
|
|
|
if (mas.last > max) {
|
|
upper_index = max + 1;
|
|
upper_last = mas.last;
|
|
|
|
upper = kmemdup(&entry[max - mas.index + 1],
|
|
((mas.last - max) *
|
|
sizeof(unsigned long)),
|
|
map->alloc_flags);
|
|
if (!upper) {
|
|
ret = -ENOMEM;
|
|
goto out_unlocked;
|
|
}
|
|
}
|
|
|
|
kfree(entry);
|
|
mas_lock(&mas);
|
|
mas_erase(&mas);
|
|
|
|
/* Insert new nodes with the saved data */
|
|
if (lower) {
|
|
mas_set_range(&mas, lower_index, lower_last);
|
|
ret = mas_store_gfp(&mas, lower, map->alloc_flags);
|
|
if (ret != 0)
|
|
goto out;
|
|
lower = NULL;
|
|
}
|
|
|
|
if (upper) {
|
|
mas_set_range(&mas, upper_index, upper_last);
|
|
ret = mas_store_gfp(&mas, upper, map->alloc_flags);
|
|
if (ret != 0)
|
|
goto out;
|
|
upper = NULL;
|
|
}
|
|
}
|
|
|
|
out:
|
|
mas_unlock(&mas);
|
|
out_unlocked:
|
|
kfree(lower);
|
|
kfree(upper);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int regcache_maple_sync_block(struct regmap *map, unsigned long *entry,
|
|
struct ma_state *mas,
|
|
unsigned int min, unsigned int max)
|
|
{
|
|
void *buf;
|
|
unsigned long r;
|
|
size_t val_bytes = map->format.val_bytes;
|
|
int ret = 0;
|
|
|
|
mas_pause(mas);
|
|
rcu_read_unlock();
|
|
|
|
/*
|
|
* Use a raw write if writing more than one register to a
|
|
* device that supports raw writes to reduce transaction
|
|
* overheads.
|
|
*/
|
|
if (max - min > 1 && regmap_can_raw_write(map)) {
|
|
buf = kmalloc(val_bytes * (max - min), map->alloc_flags);
|
|
if (!buf) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
/* Render the data for a raw write */
|
|
for (r = min; r < max; r++) {
|
|
regcache_set_val(map, buf, r - min,
|
|
entry[r - mas->index]);
|
|
}
|
|
|
|
ret = _regmap_raw_write(map, min, buf, (max - min) * val_bytes,
|
|
false);
|
|
|
|
kfree(buf);
|
|
} else {
|
|
for (r = min; r < max; r++) {
|
|
ret = _regmap_write(map, r,
|
|
entry[r - mas->index]);
|
|
if (ret != 0)
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
out:
|
|
rcu_read_lock();
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int regcache_maple_sync(struct regmap *map, unsigned int min,
|
|
unsigned int max)
|
|
{
|
|
struct maple_tree *mt = map->cache;
|
|
unsigned long *entry;
|
|
MA_STATE(mas, mt, min, max);
|
|
unsigned long lmin = min;
|
|
unsigned long lmax = max;
|
|
unsigned int r, v, sync_start;
|
|
int ret = 0;
|
|
bool sync_needed = false;
|
|
|
|
map->cache_bypass = true;
|
|
|
|
rcu_read_lock();
|
|
|
|
mas_for_each(&mas, entry, max) {
|
|
for (r = max(mas.index, lmin); r <= min(mas.last, lmax); r++) {
|
|
v = entry[r - mas.index];
|
|
|
|
if (regcache_reg_needs_sync(map, r, v)) {
|
|
if (!sync_needed) {
|
|
sync_start = r;
|
|
sync_needed = true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (!sync_needed)
|
|
continue;
|
|
|
|
ret = regcache_maple_sync_block(map, entry, &mas,
|
|
sync_start, r);
|
|
if (ret != 0)
|
|
goto out;
|
|
sync_needed = false;
|
|
}
|
|
|
|
if (sync_needed) {
|
|
ret = regcache_maple_sync_block(map, entry, &mas,
|
|
sync_start, r);
|
|
if (ret != 0)
|
|
goto out;
|
|
sync_needed = false;
|
|
}
|
|
}
|
|
|
|
out:
|
|
rcu_read_unlock();
|
|
|
|
map->cache_bypass = false;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int regcache_maple_exit(struct regmap *map)
|
|
{
|
|
struct maple_tree *mt = map->cache;
|
|
MA_STATE(mas, mt, 0, UINT_MAX);
|
|
unsigned int *entry;
|
|
|
|
/* if we've already been called then just return */
|
|
if (!mt)
|
|
return 0;
|
|
|
|
mas_lock(&mas);
|
|
mas_for_each(&mas, entry, UINT_MAX)
|
|
kfree(entry);
|
|
__mt_destroy(mt);
|
|
mas_unlock(&mas);
|
|
|
|
kfree(mt);
|
|
map->cache = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int regcache_maple_insert_block(struct regmap *map, int first,
|
|
int last)
|
|
{
|
|
struct maple_tree *mt = map->cache;
|
|
MA_STATE(mas, mt, first, last);
|
|
unsigned long *entry;
|
|
int i, ret;
|
|
|
|
entry = kcalloc(last - first + 1, sizeof(unsigned long), map->alloc_flags);
|
|
if (!entry)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < last - first + 1; i++)
|
|
entry[i] = map->reg_defaults[first + i].def;
|
|
|
|
mas_lock(&mas);
|
|
|
|
mas_set_range(&mas, map->reg_defaults[first].reg,
|
|
map->reg_defaults[last].reg);
|
|
ret = mas_store_gfp(&mas, entry, map->alloc_flags);
|
|
|
|
mas_unlock(&mas);
|
|
|
|
if (ret)
|
|
kfree(entry);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int regcache_maple_init(struct regmap *map)
|
|
{
|
|
struct maple_tree *mt;
|
|
int i;
|
|
int ret;
|
|
int range_start;
|
|
|
|
mt = kmalloc(sizeof(*mt), GFP_KERNEL);
|
|
if (!mt)
|
|
return -ENOMEM;
|
|
map->cache = mt;
|
|
|
|
mt_init(mt);
|
|
|
|
if (!map->num_reg_defaults)
|
|
return 0;
|
|
|
|
range_start = 0;
|
|
|
|
/* Scan for ranges of contiguous registers */
|
|
for (i = 1; i < map->num_reg_defaults; i++) {
|
|
if (map->reg_defaults[i].reg !=
|
|
map->reg_defaults[i - 1].reg + 1) {
|
|
ret = regcache_maple_insert_block(map, range_start,
|
|
i - 1);
|
|
if (ret != 0)
|
|
goto err;
|
|
|
|
range_start = i;
|
|
}
|
|
}
|
|
|
|
/* Add the last block */
|
|
ret = regcache_maple_insert_block(map, range_start,
|
|
map->num_reg_defaults - 1);
|
|
if (ret != 0)
|
|
goto err;
|
|
|
|
return 0;
|
|
|
|
err:
|
|
regcache_maple_exit(map);
|
|
return ret;
|
|
}
|
|
|
|
struct regcache_ops regcache_maple_ops = {
|
|
.type = REGCACHE_MAPLE,
|
|
.name = "maple",
|
|
.init = regcache_maple_init,
|
|
.exit = regcache_maple_exit,
|
|
.read = regcache_maple_read,
|
|
.write = regcache_maple_write,
|
|
.drop = regcache_maple_drop,
|
|
.sync = regcache_maple_sync,
|
|
};
|