|
@@ -819,4 +819,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 */
|