diff options
Diffstat (limited to 'include/linux/rhashtable.h')
| -rw-r--r-- | include/linux/rhashtable.h | 100 | 
1 files changed, 93 insertions, 7 deletions
| diff --git a/include/linux/rhashtable.h b/include/linux/rhashtable.h index 843ceca9a21e..63bd7601b6de 100644 --- a/include/linux/rhashtable.h +++ b/include/linux/rhashtable.h @@ -19,6 +19,7 @@  #include <linux/atomic.h>  #include <linux/compiler.h> +#include <linux/err.h>  #include <linux/errno.h>  #include <linux/jhash.h>  #include <linux/list_nulls.h> @@ -339,10 +340,11 @@ static inline int lockdep_rht_bucket_is_held(const struct bucket_table *tbl,  int rhashtable_init(struct rhashtable *ht,  		    const struct rhashtable_params *params); -int rhashtable_insert_slow(struct rhashtable *ht, const void *key, -			   struct rhash_head *obj, -			   struct bucket_table *old_tbl); -int rhashtable_insert_rehash(struct rhashtable *ht); +struct bucket_table *rhashtable_insert_slow(struct rhashtable *ht, +					    const void *key, +					    struct rhash_head *obj, +					    struct bucket_table *old_tbl); +int rhashtable_insert_rehash(struct rhashtable *ht, struct bucket_table *tbl);  int rhashtable_walk_init(struct rhashtable *ht, struct rhashtable_iter *iter);  void rhashtable_walk_exit(struct rhashtable_iter *iter); @@ -598,9 +600,11 @@ restart:  	new_tbl = rht_dereference_rcu(tbl->future_tbl, ht);  	if (unlikely(new_tbl)) { -		err = rhashtable_insert_slow(ht, key, obj, new_tbl); -		if (err == -EAGAIN) +		tbl = rhashtable_insert_slow(ht, key, obj, new_tbl); +		if (!IS_ERR_OR_NULL(tbl))  			goto slow_path; + +		err = PTR_ERR(tbl);  		goto out;  	} @@ -611,7 +615,7 @@ restart:  	if (unlikely(rht_grow_above_100(ht, tbl))) {  slow_path:  		spin_unlock_bh(lock); -		err = rhashtable_insert_rehash(ht); +		err = rhashtable_insert_rehash(ht, tbl);  		rcu_read_unlock();  		if (err)  			return err; @@ -819,4 +823,86 @@ out:  	return err;  } +/* Internal function, please use rhashtable_replace_fast() instead */ +static inline int __rhashtable_replace_fast( +	struct rhashtable *ht, struct bucket_table *tbl, +	struct rhash_head *obj_old, struct rhash_head *obj_new, +	const struct rhashtable_params params) +{ +	struct rhash_head __rcu **pprev; +	struct rhash_head *he; +	spinlock_t *lock; +	unsigned int hash; +	int err = -ENOENT; + +	/* Minimally, the old and new objects must have same hash +	 * (which should mean identifiers are the same). +	 */ +	hash = rht_head_hashfn(ht, tbl, obj_old, params); +	if (hash != rht_head_hashfn(ht, tbl, obj_new, params)) +		return -EINVAL; + +	lock = rht_bucket_lock(tbl, hash); + +	spin_lock_bh(lock); + +	pprev = &tbl->buckets[hash]; +	rht_for_each(he, tbl, hash) { +		if (he != obj_old) { +			pprev = &he->next; +			continue; +		} + +		rcu_assign_pointer(obj_new->next, obj_old->next); +		rcu_assign_pointer(*pprev, obj_new); +		err = 0; +		break; +	} + +	spin_unlock_bh(lock); + +	return err; +} + +/** + * rhashtable_replace_fast - replace an object in hash table + * @ht:		hash table + * @obj_old:	pointer to hash head inside object being replaced + * @obj_new:	pointer to hash head inside object which is new + * @params:	hash table parameters + * + * Replacing an object doesn't affect the number of elements in the hash table + * or bucket, so we don't need to worry about shrinking or expanding the + * table here. + * + * Returns zero on success, -ENOENT if the entry could not be found, + * -EINVAL if hash is not the same for the old and new objects. + */ +static inline int rhashtable_replace_fast( +	struct rhashtable *ht, struct rhash_head *obj_old, +	struct rhash_head *obj_new, +	const struct rhashtable_params params) +{ +	struct bucket_table *tbl; +	int err; + +	rcu_read_lock(); + +	tbl = rht_dereference_rcu(ht->tbl, ht); + +	/* Because we have already taken (and released) the bucket +	 * lock in old_tbl, if we find that future_tbl is not yet +	 * visible then that guarantees the entry to still be in +	 * the old tbl if it exists. +	 */ +	while ((err = __rhashtable_replace_fast(ht, tbl, obj_old, +						obj_new, params)) && +	       (tbl = rht_dereference_rcu(tbl->future_tbl, ht))) +		; + +	rcu_read_unlock(); + +	return err; +} +  #endif /* _LINUX_RHASHTABLE_H */ | 
