| // 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, |
| }; |