b0393e1fe4
REGCACHE_MAPLE needs to allocate memory for regmap operations.
This results in lockdep splats if used with fast_io since fast_io uses
spinlocks for locking.
BUG: sleeping function called from invalid context at include/linux/sched/mm.h:306
in_atomic(): 1, irqs_disabled(): 128, non_block: 0, pid: 167, name: kunit_try_catch
preempt_count: 1, expected: 0
1 lock held by kunit_try_catch/167:
#0: 838e9c10 (regmap_kunit:86:(config)->lock){....}-{2:2}, at: regmap_lock_spinlock+0x14/0x1c
irq event stamp: 146
hardirqs last enabled at (145): [<8078bfa8>] crng_make_state+0x1a0/0x294
hardirqs last disabled at (146): [<80c5f62c>] _raw_spin_lock_irqsave+0x7c/0x80
softirqs last enabled at (0): [<80110cc4>] copy_process+0x810/0x216c
softirqs last disabled at (0): [<00000000>] 0x0
CPU: 0 PID: 167 Comm: kunit_try_catch Tainted: G N 6.5.0-rc1-00028-gc4be22597a36-dirty #6
Hardware name: Generic DT based system
unwind_backtrace from show_stack+0x18/0x1c
show_stack from dump_stack_lvl+0x38/0x5c
dump_stack_lvl from __might_resched+0x188/0x2d0
__might_resched from __kmem_cache_alloc_node+0x1f4/0x258
__kmem_cache_alloc_node from __kmalloc+0x48/0x170
__kmalloc from regcache_maple_write+0x194/0x248
regcache_maple_write from _regmap_write+0x88/0x140
_regmap_write from regmap_write+0x44/0x68
regmap_write from basic_read_write+0x8c/0x27c
basic_read_write from kunit_generic_run_threadfn_adapter+0x1c/0x28
kunit_generic_run_threadfn_adapter from kthread+0xf8/0x120
kthread from ret_from_fork+0x14/0x3c
Exception stack(0x881a5fb0 to 0x881a5ff8)
5fa0: 00000000 00000000 00000000 00000000
5fc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
5fe0: 00000000 00000000 00000000 00000000 00000013 00000000
Use map->alloc_flags instead of GFP_KERNEL for memory allocations to fix
the problem.
Fixes: f033c26de5
("regmap: Add maple tree based register cache")
Cc: Dan Carpenter <dan.carpenter@linaro.org>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Link: https://lore.kernel.org/r/20230720172021.2617326-1-linux@roeck-us.net
Signed-off-by: Mark Brown <broonie@kernel.org>
399 lines
7.6 KiB
C
399 lines
7.6 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;
|
|
|
|
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 + 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;
|
|
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,
|
|
};
|