282 lines · 5.3 KB · C
RawDownload
// 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),			GFP_KERNEL);	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, GFP_KERNEL); 	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)),					GFP_KERNEL);			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)),					GFP_KERNEL);			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, GFP_KERNEL);			if (ret != 0)				goto out;			lower = NULL;		} 		if (upper) {			mas_set_range(&mas, upper_index, upper_last);			ret = mas_store_gfp(&mas, upper, GFP_KERNEL);			if (ret != 0)				goto out;			upper = NULL;		}	} out:	mas_unlock(&mas);out_unlocked:	kfree(lower);	kfree(upper); 	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;	int ret; 	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++) {			mas_pause(&mas);			rcu_read_unlock();			ret = regcache_sync_val(map, r, entry[r - mas.index]);			if (ret != 0)				goto out;			rcu_read_lock();		}	} 	rcu_read_unlock(); out:	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_init(struct regmap *map){	struct maple_tree *mt;	int i;	int ret; 	mt = kmalloc(sizeof(*mt), GFP_KERNEL);	if (!mt)		return -ENOMEM;	map->cache = mt; 	mt_init(mt); 	for (i = 0; i < map->num_reg_defaults; i++) {		ret = regcache_maple_write(map,					   map->reg_defaults[i].reg,					   map->reg_defaults[i].def);		if (ret)			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,};