162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* Generic associative array implementation. 362306a36Sopenharmony_ci * 462306a36Sopenharmony_ci * See Documentation/core-api/assoc_array.rst for information. 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Copyright (C) 2013 Red Hat, Inc. All Rights Reserved. 762306a36Sopenharmony_ci * Written by David Howells (dhowells@redhat.com) 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci//#define DEBUG 1062306a36Sopenharmony_ci#include <linux/rcupdate.h> 1162306a36Sopenharmony_ci#include <linux/slab.h> 1262306a36Sopenharmony_ci#include <linux/err.h> 1362306a36Sopenharmony_ci#include <linux/assoc_array_priv.h> 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci/* 1662306a36Sopenharmony_ci * Iterate over an associative array. The caller must hold the RCU read lock 1762306a36Sopenharmony_ci * or better. 1862306a36Sopenharmony_ci */ 1962306a36Sopenharmony_cistatic int assoc_array_subtree_iterate(const struct assoc_array_ptr *root, 2062306a36Sopenharmony_ci const struct assoc_array_ptr *stop, 2162306a36Sopenharmony_ci int (*iterator)(const void *leaf, 2262306a36Sopenharmony_ci void *iterator_data), 2362306a36Sopenharmony_ci void *iterator_data) 2462306a36Sopenharmony_ci{ 2562306a36Sopenharmony_ci const struct assoc_array_shortcut *shortcut; 2662306a36Sopenharmony_ci const struct assoc_array_node *node; 2762306a36Sopenharmony_ci const struct assoc_array_ptr *cursor, *ptr, *parent; 2862306a36Sopenharmony_ci unsigned long has_meta; 2962306a36Sopenharmony_ci int slot, ret; 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci cursor = root; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_cibegin_node: 3462306a36Sopenharmony_ci if (assoc_array_ptr_is_shortcut(cursor)) { 3562306a36Sopenharmony_ci /* Descend through a shortcut */ 3662306a36Sopenharmony_ci shortcut = assoc_array_ptr_to_shortcut(cursor); 3762306a36Sopenharmony_ci cursor = READ_ONCE(shortcut->next_node); /* Address dependency. */ 3862306a36Sopenharmony_ci } 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci node = assoc_array_ptr_to_node(cursor); 4162306a36Sopenharmony_ci slot = 0; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci /* We perform two passes of each node. 4462306a36Sopenharmony_ci * 4562306a36Sopenharmony_ci * The first pass does all the leaves in this node. This means we 4662306a36Sopenharmony_ci * don't miss any leaves if the node is split up by insertion whilst 4762306a36Sopenharmony_ci * we're iterating over the branches rooted here (we may, however, see 4862306a36Sopenharmony_ci * some leaves twice). 4962306a36Sopenharmony_ci */ 5062306a36Sopenharmony_ci has_meta = 0; 5162306a36Sopenharmony_ci for (; slot < ASSOC_ARRAY_FAN_OUT; slot++) { 5262306a36Sopenharmony_ci ptr = READ_ONCE(node->slots[slot]); /* Address dependency. */ 5362306a36Sopenharmony_ci has_meta |= (unsigned long)ptr; 5462306a36Sopenharmony_ci if (ptr && assoc_array_ptr_is_leaf(ptr)) { 5562306a36Sopenharmony_ci /* We need a barrier between the read of the pointer, 5662306a36Sopenharmony_ci * which is supplied by the above READ_ONCE(). 5762306a36Sopenharmony_ci */ 5862306a36Sopenharmony_ci /* Invoke the callback */ 5962306a36Sopenharmony_ci ret = iterator(assoc_array_ptr_to_leaf(ptr), 6062306a36Sopenharmony_ci iterator_data); 6162306a36Sopenharmony_ci if (ret) 6262306a36Sopenharmony_ci return ret; 6362306a36Sopenharmony_ci } 6462306a36Sopenharmony_ci } 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci /* The second pass attends to all the metadata pointers. If we follow 6762306a36Sopenharmony_ci * one of these we may find that we don't come back here, but rather go 6862306a36Sopenharmony_ci * back to a replacement node with the leaves in a different layout. 6962306a36Sopenharmony_ci * 7062306a36Sopenharmony_ci * We are guaranteed to make progress, however, as the slot number for 7162306a36Sopenharmony_ci * a particular portion of the key space cannot change - and we 7262306a36Sopenharmony_ci * continue at the back pointer + 1. 7362306a36Sopenharmony_ci */ 7462306a36Sopenharmony_ci if (!(has_meta & ASSOC_ARRAY_PTR_META_TYPE)) 7562306a36Sopenharmony_ci goto finished_node; 7662306a36Sopenharmony_ci slot = 0; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_cicontinue_node: 7962306a36Sopenharmony_ci node = assoc_array_ptr_to_node(cursor); 8062306a36Sopenharmony_ci for (; slot < ASSOC_ARRAY_FAN_OUT; slot++) { 8162306a36Sopenharmony_ci ptr = READ_ONCE(node->slots[slot]); /* Address dependency. */ 8262306a36Sopenharmony_ci if (assoc_array_ptr_is_meta(ptr)) { 8362306a36Sopenharmony_ci cursor = ptr; 8462306a36Sopenharmony_ci goto begin_node; 8562306a36Sopenharmony_ci } 8662306a36Sopenharmony_ci } 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_cifinished_node: 8962306a36Sopenharmony_ci /* Move up to the parent (may need to skip back over a shortcut) */ 9062306a36Sopenharmony_ci parent = READ_ONCE(node->back_pointer); /* Address dependency. */ 9162306a36Sopenharmony_ci slot = node->parent_slot; 9262306a36Sopenharmony_ci if (parent == stop) 9362306a36Sopenharmony_ci return 0; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci if (assoc_array_ptr_is_shortcut(parent)) { 9662306a36Sopenharmony_ci shortcut = assoc_array_ptr_to_shortcut(parent); 9762306a36Sopenharmony_ci cursor = parent; 9862306a36Sopenharmony_ci parent = READ_ONCE(shortcut->back_pointer); /* Address dependency. */ 9962306a36Sopenharmony_ci slot = shortcut->parent_slot; 10062306a36Sopenharmony_ci if (parent == stop) 10162306a36Sopenharmony_ci return 0; 10262306a36Sopenharmony_ci } 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci /* Ascend to next slot in parent node */ 10562306a36Sopenharmony_ci cursor = parent; 10662306a36Sopenharmony_ci slot++; 10762306a36Sopenharmony_ci goto continue_node; 10862306a36Sopenharmony_ci} 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci/** 11162306a36Sopenharmony_ci * assoc_array_iterate - Pass all objects in the array to a callback 11262306a36Sopenharmony_ci * @array: The array to iterate over. 11362306a36Sopenharmony_ci * @iterator: The callback function. 11462306a36Sopenharmony_ci * @iterator_data: Private data for the callback function. 11562306a36Sopenharmony_ci * 11662306a36Sopenharmony_ci * Iterate over all the objects in an associative array. Each one will be 11762306a36Sopenharmony_ci * presented to the iterator function. 11862306a36Sopenharmony_ci * 11962306a36Sopenharmony_ci * If the array is being modified concurrently with the iteration then it is 12062306a36Sopenharmony_ci * possible that some objects in the array will be passed to the iterator 12162306a36Sopenharmony_ci * callback more than once - though every object should be passed at least 12262306a36Sopenharmony_ci * once. If this is undesirable then the caller must lock against modification 12362306a36Sopenharmony_ci * for the duration of this function. 12462306a36Sopenharmony_ci * 12562306a36Sopenharmony_ci * The function will return 0 if no objects were in the array or else it will 12662306a36Sopenharmony_ci * return the result of the last iterator function called. Iteration stops 12762306a36Sopenharmony_ci * immediately if any call to the iteration function results in a non-zero 12862306a36Sopenharmony_ci * return. 12962306a36Sopenharmony_ci * 13062306a36Sopenharmony_ci * The caller should hold the RCU read lock or better if concurrent 13162306a36Sopenharmony_ci * modification is possible. 13262306a36Sopenharmony_ci */ 13362306a36Sopenharmony_ciint assoc_array_iterate(const struct assoc_array *array, 13462306a36Sopenharmony_ci int (*iterator)(const void *object, 13562306a36Sopenharmony_ci void *iterator_data), 13662306a36Sopenharmony_ci void *iterator_data) 13762306a36Sopenharmony_ci{ 13862306a36Sopenharmony_ci struct assoc_array_ptr *root = READ_ONCE(array->root); /* Address dependency. */ 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci if (!root) 14162306a36Sopenharmony_ci return 0; 14262306a36Sopenharmony_ci return assoc_array_subtree_iterate(root, NULL, iterator, iterator_data); 14362306a36Sopenharmony_ci} 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_cienum assoc_array_walk_status { 14662306a36Sopenharmony_ci assoc_array_walk_tree_empty, 14762306a36Sopenharmony_ci assoc_array_walk_found_terminal_node, 14862306a36Sopenharmony_ci assoc_array_walk_found_wrong_shortcut, 14962306a36Sopenharmony_ci}; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_cistruct assoc_array_walk_result { 15262306a36Sopenharmony_ci struct { 15362306a36Sopenharmony_ci struct assoc_array_node *node; /* Node in which leaf might be found */ 15462306a36Sopenharmony_ci int level; 15562306a36Sopenharmony_ci int slot; 15662306a36Sopenharmony_ci } terminal_node; 15762306a36Sopenharmony_ci struct { 15862306a36Sopenharmony_ci struct assoc_array_shortcut *shortcut; 15962306a36Sopenharmony_ci int level; 16062306a36Sopenharmony_ci int sc_level; 16162306a36Sopenharmony_ci unsigned long sc_segments; 16262306a36Sopenharmony_ci unsigned long dissimilarity; 16362306a36Sopenharmony_ci } wrong_shortcut; 16462306a36Sopenharmony_ci}; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci/* 16762306a36Sopenharmony_ci * Navigate through the internal tree looking for the closest node to the key. 16862306a36Sopenharmony_ci */ 16962306a36Sopenharmony_cistatic enum assoc_array_walk_status 17062306a36Sopenharmony_ciassoc_array_walk(const struct assoc_array *array, 17162306a36Sopenharmony_ci const struct assoc_array_ops *ops, 17262306a36Sopenharmony_ci const void *index_key, 17362306a36Sopenharmony_ci struct assoc_array_walk_result *result) 17462306a36Sopenharmony_ci{ 17562306a36Sopenharmony_ci struct assoc_array_shortcut *shortcut; 17662306a36Sopenharmony_ci struct assoc_array_node *node; 17762306a36Sopenharmony_ci struct assoc_array_ptr *cursor, *ptr; 17862306a36Sopenharmony_ci unsigned long sc_segments, dissimilarity; 17962306a36Sopenharmony_ci unsigned long segments; 18062306a36Sopenharmony_ci int level, sc_level, next_sc_level; 18162306a36Sopenharmony_ci int slot; 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci pr_devel("-->%s()\n", __func__); 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci cursor = READ_ONCE(array->root); /* Address dependency. */ 18662306a36Sopenharmony_ci if (!cursor) 18762306a36Sopenharmony_ci return assoc_array_walk_tree_empty; 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci level = 0; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci /* Use segments from the key for the new leaf to navigate through the 19262306a36Sopenharmony_ci * internal tree, skipping through nodes and shortcuts that are on 19362306a36Sopenharmony_ci * route to the destination. Eventually we'll come to a slot that is 19462306a36Sopenharmony_ci * either empty or contains a leaf at which point we've found a node in 19562306a36Sopenharmony_ci * which the leaf we're looking for might be found or into which it 19662306a36Sopenharmony_ci * should be inserted. 19762306a36Sopenharmony_ci */ 19862306a36Sopenharmony_cijumped: 19962306a36Sopenharmony_ci segments = ops->get_key_chunk(index_key, level); 20062306a36Sopenharmony_ci pr_devel("segments[%d]: %lx\n", level, segments); 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci if (assoc_array_ptr_is_shortcut(cursor)) 20362306a36Sopenharmony_ci goto follow_shortcut; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ciconsider_node: 20662306a36Sopenharmony_ci node = assoc_array_ptr_to_node(cursor); 20762306a36Sopenharmony_ci slot = segments >> (level & ASSOC_ARRAY_KEY_CHUNK_MASK); 20862306a36Sopenharmony_ci slot &= ASSOC_ARRAY_FAN_MASK; 20962306a36Sopenharmony_ci ptr = READ_ONCE(node->slots[slot]); /* Address dependency. */ 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci pr_devel("consider slot %x [ix=%d type=%lu]\n", 21262306a36Sopenharmony_ci slot, level, (unsigned long)ptr & 3); 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci if (!assoc_array_ptr_is_meta(ptr)) { 21562306a36Sopenharmony_ci /* The node doesn't have a node/shortcut pointer in the slot 21662306a36Sopenharmony_ci * corresponding to the index key that we have to follow. 21762306a36Sopenharmony_ci */ 21862306a36Sopenharmony_ci result->terminal_node.node = node; 21962306a36Sopenharmony_ci result->terminal_node.level = level; 22062306a36Sopenharmony_ci result->terminal_node.slot = slot; 22162306a36Sopenharmony_ci pr_devel("<--%s() = terminal_node\n", __func__); 22262306a36Sopenharmony_ci return assoc_array_walk_found_terminal_node; 22362306a36Sopenharmony_ci } 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci if (assoc_array_ptr_is_node(ptr)) { 22662306a36Sopenharmony_ci /* There is a pointer to a node in the slot corresponding to 22762306a36Sopenharmony_ci * this index key segment, so we need to follow it. 22862306a36Sopenharmony_ci */ 22962306a36Sopenharmony_ci cursor = ptr; 23062306a36Sopenharmony_ci level += ASSOC_ARRAY_LEVEL_STEP; 23162306a36Sopenharmony_ci if ((level & ASSOC_ARRAY_KEY_CHUNK_MASK) != 0) 23262306a36Sopenharmony_ci goto consider_node; 23362306a36Sopenharmony_ci goto jumped; 23462306a36Sopenharmony_ci } 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci /* There is a shortcut in the slot corresponding to the index key 23762306a36Sopenharmony_ci * segment. We follow the shortcut if its partial index key matches 23862306a36Sopenharmony_ci * this leaf's. Otherwise we need to split the shortcut. 23962306a36Sopenharmony_ci */ 24062306a36Sopenharmony_ci cursor = ptr; 24162306a36Sopenharmony_cifollow_shortcut: 24262306a36Sopenharmony_ci shortcut = assoc_array_ptr_to_shortcut(cursor); 24362306a36Sopenharmony_ci pr_devel("shortcut to %d\n", shortcut->skip_to_level); 24462306a36Sopenharmony_ci sc_level = level + ASSOC_ARRAY_LEVEL_STEP; 24562306a36Sopenharmony_ci BUG_ON(sc_level > shortcut->skip_to_level); 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci do { 24862306a36Sopenharmony_ci /* Check the leaf against the shortcut's index key a word at a 24962306a36Sopenharmony_ci * time, trimming the final word (the shortcut stores the index 25062306a36Sopenharmony_ci * key completely from the root to the shortcut's target). 25162306a36Sopenharmony_ci */ 25262306a36Sopenharmony_ci if ((sc_level & ASSOC_ARRAY_KEY_CHUNK_MASK) == 0) 25362306a36Sopenharmony_ci segments = ops->get_key_chunk(index_key, sc_level); 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci sc_segments = shortcut->index_key[sc_level >> ASSOC_ARRAY_KEY_CHUNK_SHIFT]; 25662306a36Sopenharmony_ci dissimilarity = segments ^ sc_segments; 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci if (round_up(sc_level, ASSOC_ARRAY_KEY_CHUNK_SIZE) > shortcut->skip_to_level) { 25962306a36Sopenharmony_ci /* Trim segments that are beyond the shortcut */ 26062306a36Sopenharmony_ci int shift = shortcut->skip_to_level & ASSOC_ARRAY_KEY_CHUNK_MASK; 26162306a36Sopenharmony_ci dissimilarity &= ~(ULONG_MAX << shift); 26262306a36Sopenharmony_ci next_sc_level = shortcut->skip_to_level; 26362306a36Sopenharmony_ci } else { 26462306a36Sopenharmony_ci next_sc_level = sc_level + ASSOC_ARRAY_KEY_CHUNK_SIZE; 26562306a36Sopenharmony_ci next_sc_level = round_down(next_sc_level, ASSOC_ARRAY_KEY_CHUNK_SIZE); 26662306a36Sopenharmony_ci } 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci if (dissimilarity != 0) { 26962306a36Sopenharmony_ci /* This shortcut points elsewhere */ 27062306a36Sopenharmony_ci result->wrong_shortcut.shortcut = shortcut; 27162306a36Sopenharmony_ci result->wrong_shortcut.level = level; 27262306a36Sopenharmony_ci result->wrong_shortcut.sc_level = sc_level; 27362306a36Sopenharmony_ci result->wrong_shortcut.sc_segments = sc_segments; 27462306a36Sopenharmony_ci result->wrong_shortcut.dissimilarity = dissimilarity; 27562306a36Sopenharmony_ci return assoc_array_walk_found_wrong_shortcut; 27662306a36Sopenharmony_ci } 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci sc_level = next_sc_level; 27962306a36Sopenharmony_ci } while (sc_level < shortcut->skip_to_level); 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci /* The shortcut matches the leaf's index to this point. */ 28262306a36Sopenharmony_ci cursor = READ_ONCE(shortcut->next_node); /* Address dependency. */ 28362306a36Sopenharmony_ci if (((level ^ sc_level) & ~ASSOC_ARRAY_KEY_CHUNK_MASK) != 0) { 28462306a36Sopenharmony_ci level = sc_level; 28562306a36Sopenharmony_ci goto jumped; 28662306a36Sopenharmony_ci } else { 28762306a36Sopenharmony_ci level = sc_level; 28862306a36Sopenharmony_ci goto consider_node; 28962306a36Sopenharmony_ci } 29062306a36Sopenharmony_ci} 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci/** 29362306a36Sopenharmony_ci * assoc_array_find - Find an object by index key 29462306a36Sopenharmony_ci * @array: The associative array to search. 29562306a36Sopenharmony_ci * @ops: The operations to use. 29662306a36Sopenharmony_ci * @index_key: The key to the object. 29762306a36Sopenharmony_ci * 29862306a36Sopenharmony_ci * Find an object in an associative array by walking through the internal tree 29962306a36Sopenharmony_ci * to the node that should contain the object and then searching the leaves 30062306a36Sopenharmony_ci * there. NULL is returned if the requested object was not found in the array. 30162306a36Sopenharmony_ci * 30262306a36Sopenharmony_ci * The caller must hold the RCU read lock or better. 30362306a36Sopenharmony_ci */ 30462306a36Sopenharmony_civoid *assoc_array_find(const struct assoc_array *array, 30562306a36Sopenharmony_ci const struct assoc_array_ops *ops, 30662306a36Sopenharmony_ci const void *index_key) 30762306a36Sopenharmony_ci{ 30862306a36Sopenharmony_ci struct assoc_array_walk_result result; 30962306a36Sopenharmony_ci const struct assoc_array_node *node; 31062306a36Sopenharmony_ci const struct assoc_array_ptr *ptr; 31162306a36Sopenharmony_ci const void *leaf; 31262306a36Sopenharmony_ci int slot; 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci if (assoc_array_walk(array, ops, index_key, &result) != 31562306a36Sopenharmony_ci assoc_array_walk_found_terminal_node) 31662306a36Sopenharmony_ci return NULL; 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci node = result.terminal_node.node; 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci /* If the target key is available to us, it's has to be pointed to by 32162306a36Sopenharmony_ci * the terminal node. 32262306a36Sopenharmony_ci */ 32362306a36Sopenharmony_ci for (slot = 0; slot < ASSOC_ARRAY_FAN_OUT; slot++) { 32462306a36Sopenharmony_ci ptr = READ_ONCE(node->slots[slot]); /* Address dependency. */ 32562306a36Sopenharmony_ci if (ptr && assoc_array_ptr_is_leaf(ptr)) { 32662306a36Sopenharmony_ci /* We need a barrier between the read of the pointer 32762306a36Sopenharmony_ci * and dereferencing the pointer - but only if we are 32862306a36Sopenharmony_ci * actually going to dereference it. 32962306a36Sopenharmony_ci */ 33062306a36Sopenharmony_ci leaf = assoc_array_ptr_to_leaf(ptr); 33162306a36Sopenharmony_ci if (ops->compare_object(leaf, index_key)) 33262306a36Sopenharmony_ci return (void *)leaf; 33362306a36Sopenharmony_ci } 33462306a36Sopenharmony_ci } 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci return NULL; 33762306a36Sopenharmony_ci} 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci/* 34062306a36Sopenharmony_ci * Destructively iterate over an associative array. The caller must prevent 34162306a36Sopenharmony_ci * other simultaneous accesses. 34262306a36Sopenharmony_ci */ 34362306a36Sopenharmony_cistatic void assoc_array_destroy_subtree(struct assoc_array_ptr *root, 34462306a36Sopenharmony_ci const struct assoc_array_ops *ops) 34562306a36Sopenharmony_ci{ 34662306a36Sopenharmony_ci struct assoc_array_shortcut *shortcut; 34762306a36Sopenharmony_ci struct assoc_array_node *node; 34862306a36Sopenharmony_ci struct assoc_array_ptr *cursor, *parent = NULL; 34962306a36Sopenharmony_ci int slot = -1; 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci pr_devel("-->%s()\n", __func__); 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci cursor = root; 35462306a36Sopenharmony_ci if (!cursor) { 35562306a36Sopenharmony_ci pr_devel("empty\n"); 35662306a36Sopenharmony_ci return; 35762306a36Sopenharmony_ci } 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_cimove_to_meta: 36062306a36Sopenharmony_ci if (assoc_array_ptr_is_shortcut(cursor)) { 36162306a36Sopenharmony_ci /* Descend through a shortcut */ 36262306a36Sopenharmony_ci pr_devel("[%d] shortcut\n", slot); 36362306a36Sopenharmony_ci BUG_ON(!assoc_array_ptr_is_shortcut(cursor)); 36462306a36Sopenharmony_ci shortcut = assoc_array_ptr_to_shortcut(cursor); 36562306a36Sopenharmony_ci BUG_ON(shortcut->back_pointer != parent); 36662306a36Sopenharmony_ci BUG_ON(slot != -1 && shortcut->parent_slot != slot); 36762306a36Sopenharmony_ci parent = cursor; 36862306a36Sopenharmony_ci cursor = shortcut->next_node; 36962306a36Sopenharmony_ci slot = -1; 37062306a36Sopenharmony_ci BUG_ON(!assoc_array_ptr_is_node(cursor)); 37162306a36Sopenharmony_ci } 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci pr_devel("[%d] node\n", slot); 37462306a36Sopenharmony_ci node = assoc_array_ptr_to_node(cursor); 37562306a36Sopenharmony_ci BUG_ON(node->back_pointer != parent); 37662306a36Sopenharmony_ci BUG_ON(slot != -1 && node->parent_slot != slot); 37762306a36Sopenharmony_ci slot = 0; 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_cicontinue_node: 38062306a36Sopenharmony_ci pr_devel("Node %p [back=%p]\n", node, node->back_pointer); 38162306a36Sopenharmony_ci for (; slot < ASSOC_ARRAY_FAN_OUT; slot++) { 38262306a36Sopenharmony_ci struct assoc_array_ptr *ptr = node->slots[slot]; 38362306a36Sopenharmony_ci if (!ptr) 38462306a36Sopenharmony_ci continue; 38562306a36Sopenharmony_ci if (assoc_array_ptr_is_meta(ptr)) { 38662306a36Sopenharmony_ci parent = cursor; 38762306a36Sopenharmony_ci cursor = ptr; 38862306a36Sopenharmony_ci goto move_to_meta; 38962306a36Sopenharmony_ci } 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci if (ops) { 39262306a36Sopenharmony_ci pr_devel("[%d] free leaf\n", slot); 39362306a36Sopenharmony_ci ops->free_object(assoc_array_ptr_to_leaf(ptr)); 39462306a36Sopenharmony_ci } 39562306a36Sopenharmony_ci } 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_ci parent = node->back_pointer; 39862306a36Sopenharmony_ci slot = node->parent_slot; 39962306a36Sopenharmony_ci pr_devel("free node\n"); 40062306a36Sopenharmony_ci kfree(node); 40162306a36Sopenharmony_ci if (!parent) 40262306a36Sopenharmony_ci return; /* Done */ 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci /* Move back up to the parent (may need to free a shortcut on 40562306a36Sopenharmony_ci * the way up) */ 40662306a36Sopenharmony_ci if (assoc_array_ptr_is_shortcut(parent)) { 40762306a36Sopenharmony_ci shortcut = assoc_array_ptr_to_shortcut(parent); 40862306a36Sopenharmony_ci BUG_ON(shortcut->next_node != cursor); 40962306a36Sopenharmony_ci cursor = parent; 41062306a36Sopenharmony_ci parent = shortcut->back_pointer; 41162306a36Sopenharmony_ci slot = shortcut->parent_slot; 41262306a36Sopenharmony_ci pr_devel("free shortcut\n"); 41362306a36Sopenharmony_ci kfree(shortcut); 41462306a36Sopenharmony_ci if (!parent) 41562306a36Sopenharmony_ci return; 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_ci BUG_ON(!assoc_array_ptr_is_node(parent)); 41862306a36Sopenharmony_ci } 41962306a36Sopenharmony_ci 42062306a36Sopenharmony_ci /* Ascend to next slot in parent node */ 42162306a36Sopenharmony_ci pr_devel("ascend to %p[%d]\n", parent, slot); 42262306a36Sopenharmony_ci cursor = parent; 42362306a36Sopenharmony_ci node = assoc_array_ptr_to_node(cursor); 42462306a36Sopenharmony_ci slot++; 42562306a36Sopenharmony_ci goto continue_node; 42662306a36Sopenharmony_ci} 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_ci/** 42962306a36Sopenharmony_ci * assoc_array_destroy - Destroy an associative array 43062306a36Sopenharmony_ci * @array: The array to destroy. 43162306a36Sopenharmony_ci * @ops: The operations to use. 43262306a36Sopenharmony_ci * 43362306a36Sopenharmony_ci * Discard all metadata and free all objects in an associative array. The 43462306a36Sopenharmony_ci * array will be empty and ready to use again upon completion. This function 43562306a36Sopenharmony_ci * cannot fail. 43662306a36Sopenharmony_ci * 43762306a36Sopenharmony_ci * The caller must prevent all other accesses whilst this takes place as no 43862306a36Sopenharmony_ci * attempt is made to adjust pointers gracefully to permit RCU readlock-holding 43962306a36Sopenharmony_ci * accesses to continue. On the other hand, no memory allocation is required. 44062306a36Sopenharmony_ci */ 44162306a36Sopenharmony_civoid assoc_array_destroy(struct assoc_array *array, 44262306a36Sopenharmony_ci const struct assoc_array_ops *ops) 44362306a36Sopenharmony_ci{ 44462306a36Sopenharmony_ci assoc_array_destroy_subtree(array->root, ops); 44562306a36Sopenharmony_ci array->root = NULL; 44662306a36Sopenharmony_ci} 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_ci/* 44962306a36Sopenharmony_ci * Handle insertion into an empty tree. 45062306a36Sopenharmony_ci */ 45162306a36Sopenharmony_cistatic bool assoc_array_insert_in_empty_tree(struct assoc_array_edit *edit) 45262306a36Sopenharmony_ci{ 45362306a36Sopenharmony_ci struct assoc_array_node *new_n0; 45462306a36Sopenharmony_ci 45562306a36Sopenharmony_ci pr_devel("-->%s()\n", __func__); 45662306a36Sopenharmony_ci 45762306a36Sopenharmony_ci new_n0 = kzalloc(sizeof(struct assoc_array_node), GFP_KERNEL); 45862306a36Sopenharmony_ci if (!new_n0) 45962306a36Sopenharmony_ci return false; 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_ci edit->new_meta[0] = assoc_array_node_to_ptr(new_n0); 46262306a36Sopenharmony_ci edit->leaf_p = &new_n0->slots[0]; 46362306a36Sopenharmony_ci edit->adjust_count_on = new_n0; 46462306a36Sopenharmony_ci edit->set[0].ptr = &edit->array->root; 46562306a36Sopenharmony_ci edit->set[0].to = assoc_array_node_to_ptr(new_n0); 46662306a36Sopenharmony_ci 46762306a36Sopenharmony_ci pr_devel("<--%s() = ok [no root]\n", __func__); 46862306a36Sopenharmony_ci return true; 46962306a36Sopenharmony_ci} 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_ci/* 47262306a36Sopenharmony_ci * Handle insertion into a terminal node. 47362306a36Sopenharmony_ci */ 47462306a36Sopenharmony_cistatic bool assoc_array_insert_into_terminal_node(struct assoc_array_edit *edit, 47562306a36Sopenharmony_ci const struct assoc_array_ops *ops, 47662306a36Sopenharmony_ci const void *index_key, 47762306a36Sopenharmony_ci struct assoc_array_walk_result *result) 47862306a36Sopenharmony_ci{ 47962306a36Sopenharmony_ci struct assoc_array_shortcut *shortcut, *new_s0; 48062306a36Sopenharmony_ci struct assoc_array_node *node, *new_n0, *new_n1, *side; 48162306a36Sopenharmony_ci struct assoc_array_ptr *ptr; 48262306a36Sopenharmony_ci unsigned long dissimilarity, base_seg, blank; 48362306a36Sopenharmony_ci size_t keylen; 48462306a36Sopenharmony_ci bool have_meta; 48562306a36Sopenharmony_ci int level, diff; 48662306a36Sopenharmony_ci int slot, next_slot, free_slot, i, j; 48762306a36Sopenharmony_ci 48862306a36Sopenharmony_ci node = result->terminal_node.node; 48962306a36Sopenharmony_ci level = result->terminal_node.level; 49062306a36Sopenharmony_ci edit->segment_cache[ASSOC_ARRAY_FAN_OUT] = result->terminal_node.slot; 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci pr_devel("-->%s()\n", __func__); 49362306a36Sopenharmony_ci 49462306a36Sopenharmony_ci /* We arrived at a node which doesn't have an onward node or shortcut 49562306a36Sopenharmony_ci * pointer that we have to follow. This means that (a) the leaf we 49662306a36Sopenharmony_ci * want must go here (either by insertion or replacement) or (b) we 49762306a36Sopenharmony_ci * need to split this node and insert in one of the fragments. 49862306a36Sopenharmony_ci */ 49962306a36Sopenharmony_ci free_slot = -1; 50062306a36Sopenharmony_ci 50162306a36Sopenharmony_ci /* Firstly, we have to check the leaves in this node to see if there's 50262306a36Sopenharmony_ci * a matching one we should replace in place. 50362306a36Sopenharmony_ci */ 50462306a36Sopenharmony_ci for (i = 0; i < ASSOC_ARRAY_FAN_OUT; i++) { 50562306a36Sopenharmony_ci ptr = node->slots[i]; 50662306a36Sopenharmony_ci if (!ptr) { 50762306a36Sopenharmony_ci free_slot = i; 50862306a36Sopenharmony_ci continue; 50962306a36Sopenharmony_ci } 51062306a36Sopenharmony_ci if (assoc_array_ptr_is_leaf(ptr) && 51162306a36Sopenharmony_ci ops->compare_object(assoc_array_ptr_to_leaf(ptr), 51262306a36Sopenharmony_ci index_key)) { 51362306a36Sopenharmony_ci pr_devel("replace in slot %d\n", i); 51462306a36Sopenharmony_ci edit->leaf_p = &node->slots[i]; 51562306a36Sopenharmony_ci edit->dead_leaf = node->slots[i]; 51662306a36Sopenharmony_ci pr_devel("<--%s() = ok [replace]\n", __func__); 51762306a36Sopenharmony_ci return true; 51862306a36Sopenharmony_ci } 51962306a36Sopenharmony_ci } 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_ci /* If there is a free slot in this node then we can just insert the 52262306a36Sopenharmony_ci * leaf here. 52362306a36Sopenharmony_ci */ 52462306a36Sopenharmony_ci if (free_slot >= 0) { 52562306a36Sopenharmony_ci pr_devel("insert in free slot %d\n", free_slot); 52662306a36Sopenharmony_ci edit->leaf_p = &node->slots[free_slot]; 52762306a36Sopenharmony_ci edit->adjust_count_on = node; 52862306a36Sopenharmony_ci pr_devel("<--%s() = ok [insert]\n", __func__); 52962306a36Sopenharmony_ci return true; 53062306a36Sopenharmony_ci } 53162306a36Sopenharmony_ci 53262306a36Sopenharmony_ci /* The node has no spare slots - so we're either going to have to split 53362306a36Sopenharmony_ci * it or insert another node before it. 53462306a36Sopenharmony_ci * 53562306a36Sopenharmony_ci * Whatever, we're going to need at least two new nodes - so allocate 53662306a36Sopenharmony_ci * those now. We may also need a new shortcut, but we deal with that 53762306a36Sopenharmony_ci * when we need it. 53862306a36Sopenharmony_ci */ 53962306a36Sopenharmony_ci new_n0 = kzalloc(sizeof(struct assoc_array_node), GFP_KERNEL); 54062306a36Sopenharmony_ci if (!new_n0) 54162306a36Sopenharmony_ci return false; 54262306a36Sopenharmony_ci edit->new_meta[0] = assoc_array_node_to_ptr(new_n0); 54362306a36Sopenharmony_ci new_n1 = kzalloc(sizeof(struct assoc_array_node), GFP_KERNEL); 54462306a36Sopenharmony_ci if (!new_n1) 54562306a36Sopenharmony_ci return false; 54662306a36Sopenharmony_ci edit->new_meta[1] = assoc_array_node_to_ptr(new_n1); 54762306a36Sopenharmony_ci 54862306a36Sopenharmony_ci /* We need to find out how similar the leaves are. */ 54962306a36Sopenharmony_ci pr_devel("no spare slots\n"); 55062306a36Sopenharmony_ci have_meta = false; 55162306a36Sopenharmony_ci for (i = 0; i < ASSOC_ARRAY_FAN_OUT; i++) { 55262306a36Sopenharmony_ci ptr = node->slots[i]; 55362306a36Sopenharmony_ci if (assoc_array_ptr_is_meta(ptr)) { 55462306a36Sopenharmony_ci edit->segment_cache[i] = 0xff; 55562306a36Sopenharmony_ci have_meta = true; 55662306a36Sopenharmony_ci continue; 55762306a36Sopenharmony_ci } 55862306a36Sopenharmony_ci base_seg = ops->get_object_key_chunk( 55962306a36Sopenharmony_ci assoc_array_ptr_to_leaf(ptr), level); 56062306a36Sopenharmony_ci base_seg >>= level & ASSOC_ARRAY_KEY_CHUNK_MASK; 56162306a36Sopenharmony_ci edit->segment_cache[i] = base_seg & ASSOC_ARRAY_FAN_MASK; 56262306a36Sopenharmony_ci } 56362306a36Sopenharmony_ci 56462306a36Sopenharmony_ci if (have_meta) { 56562306a36Sopenharmony_ci pr_devel("have meta\n"); 56662306a36Sopenharmony_ci goto split_node; 56762306a36Sopenharmony_ci } 56862306a36Sopenharmony_ci 56962306a36Sopenharmony_ci /* The node contains only leaves */ 57062306a36Sopenharmony_ci dissimilarity = 0; 57162306a36Sopenharmony_ci base_seg = edit->segment_cache[0]; 57262306a36Sopenharmony_ci for (i = 1; i < ASSOC_ARRAY_FAN_OUT; i++) 57362306a36Sopenharmony_ci dissimilarity |= edit->segment_cache[i] ^ base_seg; 57462306a36Sopenharmony_ci 57562306a36Sopenharmony_ci pr_devel("only leaves; dissimilarity=%lx\n", dissimilarity); 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_ci if ((dissimilarity & ASSOC_ARRAY_FAN_MASK) == 0) { 57862306a36Sopenharmony_ci /* The old leaves all cluster in the same slot. We will need 57962306a36Sopenharmony_ci * to insert a shortcut if the new node wants to cluster with them. 58062306a36Sopenharmony_ci */ 58162306a36Sopenharmony_ci if ((edit->segment_cache[ASSOC_ARRAY_FAN_OUT] ^ base_seg) == 0) 58262306a36Sopenharmony_ci goto all_leaves_cluster_together; 58362306a36Sopenharmony_ci 58462306a36Sopenharmony_ci /* Otherwise all the old leaves cluster in the same slot, but 58562306a36Sopenharmony_ci * the new leaf wants to go into a different slot - so we 58662306a36Sopenharmony_ci * create a new node (n0) to hold the new leaf and a pointer to 58762306a36Sopenharmony_ci * a new node (n1) holding all the old leaves. 58862306a36Sopenharmony_ci * 58962306a36Sopenharmony_ci * This can be done by falling through to the node splitting 59062306a36Sopenharmony_ci * path. 59162306a36Sopenharmony_ci */ 59262306a36Sopenharmony_ci pr_devel("present leaves cluster but not new leaf\n"); 59362306a36Sopenharmony_ci } 59462306a36Sopenharmony_ci 59562306a36Sopenharmony_cisplit_node: 59662306a36Sopenharmony_ci pr_devel("split node\n"); 59762306a36Sopenharmony_ci 59862306a36Sopenharmony_ci /* We need to split the current node. The node must contain anything 59962306a36Sopenharmony_ci * from a single leaf (in the one leaf case, this leaf will cluster 60062306a36Sopenharmony_ci * with the new leaf) and the rest meta-pointers, to all leaves, some 60162306a36Sopenharmony_ci * of which may cluster. 60262306a36Sopenharmony_ci * 60362306a36Sopenharmony_ci * It won't contain the case in which all the current leaves plus the 60462306a36Sopenharmony_ci * new leaves want to cluster in the same slot. 60562306a36Sopenharmony_ci * 60662306a36Sopenharmony_ci * We need to expel at least two leaves out of a set consisting of the 60762306a36Sopenharmony_ci * leaves in the node and the new leaf. The current meta pointers can 60862306a36Sopenharmony_ci * just be copied as they shouldn't cluster with any of the leaves. 60962306a36Sopenharmony_ci * 61062306a36Sopenharmony_ci * We need a new node (n0) to replace the current one and a new node to 61162306a36Sopenharmony_ci * take the expelled nodes (n1). 61262306a36Sopenharmony_ci */ 61362306a36Sopenharmony_ci edit->set[0].to = assoc_array_node_to_ptr(new_n0); 61462306a36Sopenharmony_ci new_n0->back_pointer = node->back_pointer; 61562306a36Sopenharmony_ci new_n0->parent_slot = node->parent_slot; 61662306a36Sopenharmony_ci new_n1->back_pointer = assoc_array_node_to_ptr(new_n0); 61762306a36Sopenharmony_ci new_n1->parent_slot = -1; /* Need to calculate this */ 61862306a36Sopenharmony_ci 61962306a36Sopenharmony_cido_split_node: 62062306a36Sopenharmony_ci pr_devel("do_split_node\n"); 62162306a36Sopenharmony_ci 62262306a36Sopenharmony_ci new_n0->nr_leaves_on_branch = node->nr_leaves_on_branch; 62362306a36Sopenharmony_ci new_n1->nr_leaves_on_branch = 0; 62462306a36Sopenharmony_ci 62562306a36Sopenharmony_ci /* Begin by finding two matching leaves. There have to be at least two 62662306a36Sopenharmony_ci * that match - even if there are meta pointers - because any leaf that 62762306a36Sopenharmony_ci * would match a slot with a meta pointer in it must be somewhere 62862306a36Sopenharmony_ci * behind that meta pointer and cannot be here. Further, given N 62962306a36Sopenharmony_ci * remaining leaf slots, we now have N+1 leaves to go in them. 63062306a36Sopenharmony_ci */ 63162306a36Sopenharmony_ci for (i = 0; i < ASSOC_ARRAY_FAN_OUT; i++) { 63262306a36Sopenharmony_ci slot = edit->segment_cache[i]; 63362306a36Sopenharmony_ci if (slot != 0xff) 63462306a36Sopenharmony_ci for (j = i + 1; j < ASSOC_ARRAY_FAN_OUT + 1; j++) 63562306a36Sopenharmony_ci if (edit->segment_cache[j] == slot) 63662306a36Sopenharmony_ci goto found_slot_for_multiple_occupancy; 63762306a36Sopenharmony_ci } 63862306a36Sopenharmony_cifound_slot_for_multiple_occupancy: 63962306a36Sopenharmony_ci pr_devel("same slot: %x %x [%02x]\n", i, j, slot); 64062306a36Sopenharmony_ci BUG_ON(i >= ASSOC_ARRAY_FAN_OUT); 64162306a36Sopenharmony_ci BUG_ON(j >= ASSOC_ARRAY_FAN_OUT + 1); 64262306a36Sopenharmony_ci BUG_ON(slot >= ASSOC_ARRAY_FAN_OUT); 64362306a36Sopenharmony_ci 64462306a36Sopenharmony_ci new_n1->parent_slot = slot; 64562306a36Sopenharmony_ci 64662306a36Sopenharmony_ci /* Metadata pointers cannot change slot */ 64762306a36Sopenharmony_ci for (i = 0; i < ASSOC_ARRAY_FAN_OUT; i++) 64862306a36Sopenharmony_ci if (assoc_array_ptr_is_meta(node->slots[i])) 64962306a36Sopenharmony_ci new_n0->slots[i] = node->slots[i]; 65062306a36Sopenharmony_ci else 65162306a36Sopenharmony_ci new_n0->slots[i] = NULL; 65262306a36Sopenharmony_ci BUG_ON(new_n0->slots[slot] != NULL); 65362306a36Sopenharmony_ci new_n0->slots[slot] = assoc_array_node_to_ptr(new_n1); 65462306a36Sopenharmony_ci 65562306a36Sopenharmony_ci /* Filter the leaf pointers between the new nodes */ 65662306a36Sopenharmony_ci free_slot = -1; 65762306a36Sopenharmony_ci next_slot = 0; 65862306a36Sopenharmony_ci for (i = 0; i < ASSOC_ARRAY_FAN_OUT; i++) { 65962306a36Sopenharmony_ci if (assoc_array_ptr_is_meta(node->slots[i])) 66062306a36Sopenharmony_ci continue; 66162306a36Sopenharmony_ci if (edit->segment_cache[i] == slot) { 66262306a36Sopenharmony_ci new_n1->slots[next_slot++] = node->slots[i]; 66362306a36Sopenharmony_ci new_n1->nr_leaves_on_branch++; 66462306a36Sopenharmony_ci } else { 66562306a36Sopenharmony_ci do { 66662306a36Sopenharmony_ci free_slot++; 66762306a36Sopenharmony_ci } while (new_n0->slots[free_slot] != NULL); 66862306a36Sopenharmony_ci new_n0->slots[free_slot] = node->slots[i]; 66962306a36Sopenharmony_ci } 67062306a36Sopenharmony_ci } 67162306a36Sopenharmony_ci 67262306a36Sopenharmony_ci pr_devel("filtered: f=%x n=%x\n", free_slot, next_slot); 67362306a36Sopenharmony_ci 67462306a36Sopenharmony_ci if (edit->segment_cache[ASSOC_ARRAY_FAN_OUT] != slot) { 67562306a36Sopenharmony_ci do { 67662306a36Sopenharmony_ci free_slot++; 67762306a36Sopenharmony_ci } while (new_n0->slots[free_slot] != NULL); 67862306a36Sopenharmony_ci edit->leaf_p = &new_n0->slots[free_slot]; 67962306a36Sopenharmony_ci edit->adjust_count_on = new_n0; 68062306a36Sopenharmony_ci } else { 68162306a36Sopenharmony_ci edit->leaf_p = &new_n1->slots[next_slot++]; 68262306a36Sopenharmony_ci edit->adjust_count_on = new_n1; 68362306a36Sopenharmony_ci } 68462306a36Sopenharmony_ci 68562306a36Sopenharmony_ci BUG_ON(next_slot <= 1); 68662306a36Sopenharmony_ci 68762306a36Sopenharmony_ci edit->set_backpointers_to = assoc_array_node_to_ptr(new_n0); 68862306a36Sopenharmony_ci for (i = 0; i < ASSOC_ARRAY_FAN_OUT; i++) { 68962306a36Sopenharmony_ci if (edit->segment_cache[i] == 0xff) { 69062306a36Sopenharmony_ci ptr = node->slots[i]; 69162306a36Sopenharmony_ci BUG_ON(assoc_array_ptr_is_leaf(ptr)); 69262306a36Sopenharmony_ci if (assoc_array_ptr_is_node(ptr)) { 69362306a36Sopenharmony_ci side = assoc_array_ptr_to_node(ptr); 69462306a36Sopenharmony_ci edit->set_backpointers[i] = &side->back_pointer; 69562306a36Sopenharmony_ci } else { 69662306a36Sopenharmony_ci shortcut = assoc_array_ptr_to_shortcut(ptr); 69762306a36Sopenharmony_ci edit->set_backpointers[i] = &shortcut->back_pointer; 69862306a36Sopenharmony_ci } 69962306a36Sopenharmony_ci } 70062306a36Sopenharmony_ci } 70162306a36Sopenharmony_ci 70262306a36Sopenharmony_ci ptr = node->back_pointer; 70362306a36Sopenharmony_ci if (!ptr) 70462306a36Sopenharmony_ci edit->set[0].ptr = &edit->array->root; 70562306a36Sopenharmony_ci else if (assoc_array_ptr_is_node(ptr)) 70662306a36Sopenharmony_ci edit->set[0].ptr = &assoc_array_ptr_to_node(ptr)->slots[node->parent_slot]; 70762306a36Sopenharmony_ci else 70862306a36Sopenharmony_ci edit->set[0].ptr = &assoc_array_ptr_to_shortcut(ptr)->next_node; 70962306a36Sopenharmony_ci edit->excised_meta[0] = assoc_array_node_to_ptr(node); 71062306a36Sopenharmony_ci pr_devel("<--%s() = ok [split node]\n", __func__); 71162306a36Sopenharmony_ci return true; 71262306a36Sopenharmony_ci 71362306a36Sopenharmony_ciall_leaves_cluster_together: 71462306a36Sopenharmony_ci /* All the leaves, new and old, want to cluster together in this node 71562306a36Sopenharmony_ci * in the same slot, so we have to replace this node with a shortcut to 71662306a36Sopenharmony_ci * skip over the identical parts of the key and then place a pair of 71762306a36Sopenharmony_ci * nodes, one inside the other, at the end of the shortcut and 71862306a36Sopenharmony_ci * distribute the keys between them. 71962306a36Sopenharmony_ci * 72062306a36Sopenharmony_ci * Firstly we need to work out where the leaves start diverging as a 72162306a36Sopenharmony_ci * bit position into their keys so that we know how big the shortcut 72262306a36Sopenharmony_ci * needs to be. 72362306a36Sopenharmony_ci * 72462306a36Sopenharmony_ci * We only need to make a single pass of N of the N+1 leaves because if 72562306a36Sopenharmony_ci * any keys differ between themselves at bit X then at least one of 72662306a36Sopenharmony_ci * them must also differ with the base key at bit X or before. 72762306a36Sopenharmony_ci */ 72862306a36Sopenharmony_ci pr_devel("all leaves cluster together\n"); 72962306a36Sopenharmony_ci diff = INT_MAX; 73062306a36Sopenharmony_ci for (i = 0; i < ASSOC_ARRAY_FAN_OUT; i++) { 73162306a36Sopenharmony_ci int x = ops->diff_objects(assoc_array_ptr_to_leaf(node->slots[i]), 73262306a36Sopenharmony_ci index_key); 73362306a36Sopenharmony_ci if (x < diff) { 73462306a36Sopenharmony_ci BUG_ON(x < 0); 73562306a36Sopenharmony_ci diff = x; 73662306a36Sopenharmony_ci } 73762306a36Sopenharmony_ci } 73862306a36Sopenharmony_ci BUG_ON(diff == INT_MAX); 73962306a36Sopenharmony_ci BUG_ON(diff < level + ASSOC_ARRAY_LEVEL_STEP); 74062306a36Sopenharmony_ci 74162306a36Sopenharmony_ci keylen = round_up(diff, ASSOC_ARRAY_KEY_CHUNK_SIZE); 74262306a36Sopenharmony_ci keylen >>= ASSOC_ARRAY_KEY_CHUNK_SHIFT; 74362306a36Sopenharmony_ci 74462306a36Sopenharmony_ci new_s0 = kzalloc(struct_size(new_s0, index_key, keylen), GFP_KERNEL); 74562306a36Sopenharmony_ci if (!new_s0) 74662306a36Sopenharmony_ci return false; 74762306a36Sopenharmony_ci edit->new_meta[2] = assoc_array_shortcut_to_ptr(new_s0); 74862306a36Sopenharmony_ci 74962306a36Sopenharmony_ci edit->set[0].to = assoc_array_shortcut_to_ptr(new_s0); 75062306a36Sopenharmony_ci new_s0->back_pointer = node->back_pointer; 75162306a36Sopenharmony_ci new_s0->parent_slot = node->parent_slot; 75262306a36Sopenharmony_ci new_s0->next_node = assoc_array_node_to_ptr(new_n0); 75362306a36Sopenharmony_ci new_n0->back_pointer = assoc_array_shortcut_to_ptr(new_s0); 75462306a36Sopenharmony_ci new_n0->parent_slot = 0; 75562306a36Sopenharmony_ci new_n1->back_pointer = assoc_array_node_to_ptr(new_n0); 75662306a36Sopenharmony_ci new_n1->parent_slot = -1; /* Need to calculate this */ 75762306a36Sopenharmony_ci 75862306a36Sopenharmony_ci new_s0->skip_to_level = level = diff & ~ASSOC_ARRAY_LEVEL_STEP_MASK; 75962306a36Sopenharmony_ci pr_devel("skip_to_level = %d [diff %d]\n", level, diff); 76062306a36Sopenharmony_ci BUG_ON(level <= 0); 76162306a36Sopenharmony_ci 76262306a36Sopenharmony_ci for (i = 0; i < keylen; i++) 76362306a36Sopenharmony_ci new_s0->index_key[i] = 76462306a36Sopenharmony_ci ops->get_key_chunk(index_key, i * ASSOC_ARRAY_KEY_CHUNK_SIZE); 76562306a36Sopenharmony_ci 76662306a36Sopenharmony_ci if (level & ASSOC_ARRAY_KEY_CHUNK_MASK) { 76762306a36Sopenharmony_ci blank = ULONG_MAX << (level & ASSOC_ARRAY_KEY_CHUNK_MASK); 76862306a36Sopenharmony_ci pr_devel("blank off [%zu] %d: %lx\n", keylen - 1, level, blank); 76962306a36Sopenharmony_ci new_s0->index_key[keylen - 1] &= ~blank; 77062306a36Sopenharmony_ci } 77162306a36Sopenharmony_ci 77262306a36Sopenharmony_ci /* This now reduces to a node splitting exercise for which we'll need 77362306a36Sopenharmony_ci * to regenerate the disparity table. 77462306a36Sopenharmony_ci */ 77562306a36Sopenharmony_ci for (i = 0; i < ASSOC_ARRAY_FAN_OUT; i++) { 77662306a36Sopenharmony_ci ptr = node->slots[i]; 77762306a36Sopenharmony_ci base_seg = ops->get_object_key_chunk(assoc_array_ptr_to_leaf(ptr), 77862306a36Sopenharmony_ci level); 77962306a36Sopenharmony_ci base_seg >>= level & ASSOC_ARRAY_KEY_CHUNK_MASK; 78062306a36Sopenharmony_ci edit->segment_cache[i] = base_seg & ASSOC_ARRAY_FAN_MASK; 78162306a36Sopenharmony_ci } 78262306a36Sopenharmony_ci 78362306a36Sopenharmony_ci base_seg = ops->get_key_chunk(index_key, level); 78462306a36Sopenharmony_ci base_seg >>= level & ASSOC_ARRAY_KEY_CHUNK_MASK; 78562306a36Sopenharmony_ci edit->segment_cache[ASSOC_ARRAY_FAN_OUT] = base_seg & ASSOC_ARRAY_FAN_MASK; 78662306a36Sopenharmony_ci goto do_split_node; 78762306a36Sopenharmony_ci} 78862306a36Sopenharmony_ci 78962306a36Sopenharmony_ci/* 79062306a36Sopenharmony_ci * Handle insertion into the middle of a shortcut. 79162306a36Sopenharmony_ci */ 79262306a36Sopenharmony_cistatic bool assoc_array_insert_mid_shortcut(struct assoc_array_edit *edit, 79362306a36Sopenharmony_ci const struct assoc_array_ops *ops, 79462306a36Sopenharmony_ci struct assoc_array_walk_result *result) 79562306a36Sopenharmony_ci{ 79662306a36Sopenharmony_ci struct assoc_array_shortcut *shortcut, *new_s0, *new_s1; 79762306a36Sopenharmony_ci struct assoc_array_node *node, *new_n0, *side; 79862306a36Sopenharmony_ci unsigned long sc_segments, dissimilarity, blank; 79962306a36Sopenharmony_ci size_t keylen; 80062306a36Sopenharmony_ci int level, sc_level, diff; 80162306a36Sopenharmony_ci int sc_slot; 80262306a36Sopenharmony_ci 80362306a36Sopenharmony_ci shortcut = result->wrong_shortcut.shortcut; 80462306a36Sopenharmony_ci level = result->wrong_shortcut.level; 80562306a36Sopenharmony_ci sc_level = result->wrong_shortcut.sc_level; 80662306a36Sopenharmony_ci sc_segments = result->wrong_shortcut.sc_segments; 80762306a36Sopenharmony_ci dissimilarity = result->wrong_shortcut.dissimilarity; 80862306a36Sopenharmony_ci 80962306a36Sopenharmony_ci pr_devel("-->%s(ix=%d dis=%lx scix=%d)\n", 81062306a36Sopenharmony_ci __func__, level, dissimilarity, sc_level); 81162306a36Sopenharmony_ci 81262306a36Sopenharmony_ci /* We need to split a shortcut and insert a node between the two 81362306a36Sopenharmony_ci * pieces. Zero-length pieces will be dispensed with entirely. 81462306a36Sopenharmony_ci * 81562306a36Sopenharmony_ci * First of all, we need to find out in which level the first 81662306a36Sopenharmony_ci * difference was. 81762306a36Sopenharmony_ci */ 81862306a36Sopenharmony_ci diff = __ffs(dissimilarity); 81962306a36Sopenharmony_ci diff &= ~ASSOC_ARRAY_LEVEL_STEP_MASK; 82062306a36Sopenharmony_ci diff += sc_level & ~ASSOC_ARRAY_KEY_CHUNK_MASK; 82162306a36Sopenharmony_ci pr_devel("diff=%d\n", diff); 82262306a36Sopenharmony_ci 82362306a36Sopenharmony_ci if (!shortcut->back_pointer) { 82462306a36Sopenharmony_ci edit->set[0].ptr = &edit->array->root; 82562306a36Sopenharmony_ci } else if (assoc_array_ptr_is_node(shortcut->back_pointer)) { 82662306a36Sopenharmony_ci node = assoc_array_ptr_to_node(shortcut->back_pointer); 82762306a36Sopenharmony_ci edit->set[0].ptr = &node->slots[shortcut->parent_slot]; 82862306a36Sopenharmony_ci } else { 82962306a36Sopenharmony_ci BUG(); 83062306a36Sopenharmony_ci } 83162306a36Sopenharmony_ci 83262306a36Sopenharmony_ci edit->excised_meta[0] = assoc_array_shortcut_to_ptr(shortcut); 83362306a36Sopenharmony_ci 83462306a36Sopenharmony_ci /* Create a new node now since we're going to need it anyway */ 83562306a36Sopenharmony_ci new_n0 = kzalloc(sizeof(struct assoc_array_node), GFP_KERNEL); 83662306a36Sopenharmony_ci if (!new_n0) 83762306a36Sopenharmony_ci return false; 83862306a36Sopenharmony_ci edit->new_meta[0] = assoc_array_node_to_ptr(new_n0); 83962306a36Sopenharmony_ci edit->adjust_count_on = new_n0; 84062306a36Sopenharmony_ci 84162306a36Sopenharmony_ci /* Insert a new shortcut before the new node if this segment isn't of 84262306a36Sopenharmony_ci * zero length - otherwise we just connect the new node directly to the 84362306a36Sopenharmony_ci * parent. 84462306a36Sopenharmony_ci */ 84562306a36Sopenharmony_ci level += ASSOC_ARRAY_LEVEL_STEP; 84662306a36Sopenharmony_ci if (diff > level) { 84762306a36Sopenharmony_ci pr_devel("pre-shortcut %d...%d\n", level, diff); 84862306a36Sopenharmony_ci keylen = round_up(diff, ASSOC_ARRAY_KEY_CHUNK_SIZE); 84962306a36Sopenharmony_ci keylen >>= ASSOC_ARRAY_KEY_CHUNK_SHIFT; 85062306a36Sopenharmony_ci 85162306a36Sopenharmony_ci new_s0 = kzalloc(struct_size(new_s0, index_key, keylen), 85262306a36Sopenharmony_ci GFP_KERNEL); 85362306a36Sopenharmony_ci if (!new_s0) 85462306a36Sopenharmony_ci return false; 85562306a36Sopenharmony_ci edit->new_meta[1] = assoc_array_shortcut_to_ptr(new_s0); 85662306a36Sopenharmony_ci edit->set[0].to = assoc_array_shortcut_to_ptr(new_s0); 85762306a36Sopenharmony_ci new_s0->back_pointer = shortcut->back_pointer; 85862306a36Sopenharmony_ci new_s0->parent_slot = shortcut->parent_slot; 85962306a36Sopenharmony_ci new_s0->next_node = assoc_array_node_to_ptr(new_n0); 86062306a36Sopenharmony_ci new_s0->skip_to_level = diff; 86162306a36Sopenharmony_ci 86262306a36Sopenharmony_ci new_n0->back_pointer = assoc_array_shortcut_to_ptr(new_s0); 86362306a36Sopenharmony_ci new_n0->parent_slot = 0; 86462306a36Sopenharmony_ci 86562306a36Sopenharmony_ci memcpy(new_s0->index_key, shortcut->index_key, 86662306a36Sopenharmony_ci flex_array_size(new_s0, index_key, keylen)); 86762306a36Sopenharmony_ci 86862306a36Sopenharmony_ci blank = ULONG_MAX << (diff & ASSOC_ARRAY_KEY_CHUNK_MASK); 86962306a36Sopenharmony_ci pr_devel("blank off [%zu] %d: %lx\n", keylen - 1, diff, blank); 87062306a36Sopenharmony_ci new_s0->index_key[keylen - 1] &= ~blank; 87162306a36Sopenharmony_ci } else { 87262306a36Sopenharmony_ci pr_devel("no pre-shortcut\n"); 87362306a36Sopenharmony_ci edit->set[0].to = assoc_array_node_to_ptr(new_n0); 87462306a36Sopenharmony_ci new_n0->back_pointer = shortcut->back_pointer; 87562306a36Sopenharmony_ci new_n0->parent_slot = shortcut->parent_slot; 87662306a36Sopenharmony_ci } 87762306a36Sopenharmony_ci 87862306a36Sopenharmony_ci side = assoc_array_ptr_to_node(shortcut->next_node); 87962306a36Sopenharmony_ci new_n0->nr_leaves_on_branch = side->nr_leaves_on_branch; 88062306a36Sopenharmony_ci 88162306a36Sopenharmony_ci /* We need to know which slot in the new node is going to take a 88262306a36Sopenharmony_ci * metadata pointer. 88362306a36Sopenharmony_ci */ 88462306a36Sopenharmony_ci sc_slot = sc_segments >> (diff & ASSOC_ARRAY_KEY_CHUNK_MASK); 88562306a36Sopenharmony_ci sc_slot &= ASSOC_ARRAY_FAN_MASK; 88662306a36Sopenharmony_ci 88762306a36Sopenharmony_ci pr_devel("new slot %lx >> %d -> %d\n", 88862306a36Sopenharmony_ci sc_segments, diff & ASSOC_ARRAY_KEY_CHUNK_MASK, sc_slot); 88962306a36Sopenharmony_ci 89062306a36Sopenharmony_ci /* Determine whether we need to follow the new node with a replacement 89162306a36Sopenharmony_ci * for the current shortcut. We could in theory reuse the current 89262306a36Sopenharmony_ci * shortcut if its parent slot number doesn't change - but that's a 89362306a36Sopenharmony_ci * 1-in-16 chance so not worth expending the code upon. 89462306a36Sopenharmony_ci */ 89562306a36Sopenharmony_ci level = diff + ASSOC_ARRAY_LEVEL_STEP; 89662306a36Sopenharmony_ci if (level < shortcut->skip_to_level) { 89762306a36Sopenharmony_ci pr_devel("post-shortcut %d...%d\n", level, shortcut->skip_to_level); 89862306a36Sopenharmony_ci keylen = round_up(shortcut->skip_to_level, ASSOC_ARRAY_KEY_CHUNK_SIZE); 89962306a36Sopenharmony_ci keylen >>= ASSOC_ARRAY_KEY_CHUNK_SHIFT; 90062306a36Sopenharmony_ci 90162306a36Sopenharmony_ci new_s1 = kzalloc(struct_size(new_s1, index_key, keylen), 90262306a36Sopenharmony_ci GFP_KERNEL); 90362306a36Sopenharmony_ci if (!new_s1) 90462306a36Sopenharmony_ci return false; 90562306a36Sopenharmony_ci edit->new_meta[2] = assoc_array_shortcut_to_ptr(new_s1); 90662306a36Sopenharmony_ci 90762306a36Sopenharmony_ci new_s1->back_pointer = assoc_array_node_to_ptr(new_n0); 90862306a36Sopenharmony_ci new_s1->parent_slot = sc_slot; 90962306a36Sopenharmony_ci new_s1->next_node = shortcut->next_node; 91062306a36Sopenharmony_ci new_s1->skip_to_level = shortcut->skip_to_level; 91162306a36Sopenharmony_ci 91262306a36Sopenharmony_ci new_n0->slots[sc_slot] = assoc_array_shortcut_to_ptr(new_s1); 91362306a36Sopenharmony_ci 91462306a36Sopenharmony_ci memcpy(new_s1->index_key, shortcut->index_key, 91562306a36Sopenharmony_ci flex_array_size(new_s1, index_key, keylen)); 91662306a36Sopenharmony_ci 91762306a36Sopenharmony_ci edit->set[1].ptr = &side->back_pointer; 91862306a36Sopenharmony_ci edit->set[1].to = assoc_array_shortcut_to_ptr(new_s1); 91962306a36Sopenharmony_ci } else { 92062306a36Sopenharmony_ci pr_devel("no post-shortcut\n"); 92162306a36Sopenharmony_ci 92262306a36Sopenharmony_ci /* We don't have to replace the pointed-to node as long as we 92362306a36Sopenharmony_ci * use memory barriers to make sure the parent slot number is 92462306a36Sopenharmony_ci * changed before the back pointer (the parent slot number is 92562306a36Sopenharmony_ci * irrelevant to the old parent shortcut). 92662306a36Sopenharmony_ci */ 92762306a36Sopenharmony_ci new_n0->slots[sc_slot] = shortcut->next_node; 92862306a36Sopenharmony_ci edit->set_parent_slot[0].p = &side->parent_slot; 92962306a36Sopenharmony_ci edit->set_parent_slot[0].to = sc_slot; 93062306a36Sopenharmony_ci edit->set[1].ptr = &side->back_pointer; 93162306a36Sopenharmony_ci edit->set[1].to = assoc_array_node_to_ptr(new_n0); 93262306a36Sopenharmony_ci } 93362306a36Sopenharmony_ci 93462306a36Sopenharmony_ci /* Install the new leaf in a spare slot in the new node. */ 93562306a36Sopenharmony_ci if (sc_slot == 0) 93662306a36Sopenharmony_ci edit->leaf_p = &new_n0->slots[1]; 93762306a36Sopenharmony_ci else 93862306a36Sopenharmony_ci edit->leaf_p = &new_n0->slots[0]; 93962306a36Sopenharmony_ci 94062306a36Sopenharmony_ci pr_devel("<--%s() = ok [split shortcut]\n", __func__); 94162306a36Sopenharmony_ci return edit; 94262306a36Sopenharmony_ci} 94362306a36Sopenharmony_ci 94462306a36Sopenharmony_ci/** 94562306a36Sopenharmony_ci * assoc_array_insert - Script insertion of an object into an associative array 94662306a36Sopenharmony_ci * @array: The array to insert into. 94762306a36Sopenharmony_ci * @ops: The operations to use. 94862306a36Sopenharmony_ci * @index_key: The key to insert at. 94962306a36Sopenharmony_ci * @object: The object to insert. 95062306a36Sopenharmony_ci * 95162306a36Sopenharmony_ci * Precalculate and preallocate a script for the insertion or replacement of an 95262306a36Sopenharmony_ci * object in an associative array. This results in an edit script that can 95362306a36Sopenharmony_ci * either be applied or cancelled. 95462306a36Sopenharmony_ci * 95562306a36Sopenharmony_ci * The function returns a pointer to an edit script or -ENOMEM. 95662306a36Sopenharmony_ci * 95762306a36Sopenharmony_ci * The caller should lock against other modifications and must continue to hold 95862306a36Sopenharmony_ci * the lock until assoc_array_apply_edit() has been called. 95962306a36Sopenharmony_ci * 96062306a36Sopenharmony_ci * Accesses to the tree may take place concurrently with this function, 96162306a36Sopenharmony_ci * provided they hold the RCU read lock. 96262306a36Sopenharmony_ci */ 96362306a36Sopenharmony_cistruct assoc_array_edit *assoc_array_insert(struct assoc_array *array, 96462306a36Sopenharmony_ci const struct assoc_array_ops *ops, 96562306a36Sopenharmony_ci const void *index_key, 96662306a36Sopenharmony_ci void *object) 96762306a36Sopenharmony_ci{ 96862306a36Sopenharmony_ci struct assoc_array_walk_result result; 96962306a36Sopenharmony_ci struct assoc_array_edit *edit; 97062306a36Sopenharmony_ci 97162306a36Sopenharmony_ci pr_devel("-->%s()\n", __func__); 97262306a36Sopenharmony_ci 97362306a36Sopenharmony_ci /* The leaf pointer we're given must not have the bottom bit set as we 97462306a36Sopenharmony_ci * use those for type-marking the pointer. NULL pointers are also not 97562306a36Sopenharmony_ci * allowed as they indicate an empty slot but we have to allow them 97662306a36Sopenharmony_ci * here as they can be updated later. 97762306a36Sopenharmony_ci */ 97862306a36Sopenharmony_ci BUG_ON(assoc_array_ptr_is_meta(object)); 97962306a36Sopenharmony_ci 98062306a36Sopenharmony_ci edit = kzalloc(sizeof(struct assoc_array_edit), GFP_KERNEL); 98162306a36Sopenharmony_ci if (!edit) 98262306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 98362306a36Sopenharmony_ci edit->array = array; 98462306a36Sopenharmony_ci edit->ops = ops; 98562306a36Sopenharmony_ci edit->leaf = assoc_array_leaf_to_ptr(object); 98662306a36Sopenharmony_ci edit->adjust_count_by = 1; 98762306a36Sopenharmony_ci 98862306a36Sopenharmony_ci switch (assoc_array_walk(array, ops, index_key, &result)) { 98962306a36Sopenharmony_ci case assoc_array_walk_tree_empty: 99062306a36Sopenharmony_ci /* Allocate a root node if there isn't one yet */ 99162306a36Sopenharmony_ci if (!assoc_array_insert_in_empty_tree(edit)) 99262306a36Sopenharmony_ci goto enomem; 99362306a36Sopenharmony_ci return edit; 99462306a36Sopenharmony_ci 99562306a36Sopenharmony_ci case assoc_array_walk_found_terminal_node: 99662306a36Sopenharmony_ci /* We found a node that doesn't have a node/shortcut pointer in 99762306a36Sopenharmony_ci * the slot corresponding to the index key that we have to 99862306a36Sopenharmony_ci * follow. 99962306a36Sopenharmony_ci */ 100062306a36Sopenharmony_ci if (!assoc_array_insert_into_terminal_node(edit, ops, index_key, 100162306a36Sopenharmony_ci &result)) 100262306a36Sopenharmony_ci goto enomem; 100362306a36Sopenharmony_ci return edit; 100462306a36Sopenharmony_ci 100562306a36Sopenharmony_ci case assoc_array_walk_found_wrong_shortcut: 100662306a36Sopenharmony_ci /* We found a shortcut that didn't match our key in a slot we 100762306a36Sopenharmony_ci * needed to follow. 100862306a36Sopenharmony_ci */ 100962306a36Sopenharmony_ci if (!assoc_array_insert_mid_shortcut(edit, ops, &result)) 101062306a36Sopenharmony_ci goto enomem; 101162306a36Sopenharmony_ci return edit; 101262306a36Sopenharmony_ci } 101362306a36Sopenharmony_ci 101462306a36Sopenharmony_cienomem: 101562306a36Sopenharmony_ci /* Clean up after an out of memory error */ 101662306a36Sopenharmony_ci pr_devel("enomem\n"); 101762306a36Sopenharmony_ci assoc_array_cancel_edit(edit); 101862306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 101962306a36Sopenharmony_ci} 102062306a36Sopenharmony_ci 102162306a36Sopenharmony_ci/** 102262306a36Sopenharmony_ci * assoc_array_insert_set_object - Set the new object pointer in an edit script 102362306a36Sopenharmony_ci * @edit: The edit script to modify. 102462306a36Sopenharmony_ci * @object: The object pointer to set. 102562306a36Sopenharmony_ci * 102662306a36Sopenharmony_ci * Change the object to be inserted in an edit script. The object pointed to 102762306a36Sopenharmony_ci * by the old object is not freed. This must be done prior to applying the 102862306a36Sopenharmony_ci * script. 102962306a36Sopenharmony_ci */ 103062306a36Sopenharmony_civoid assoc_array_insert_set_object(struct assoc_array_edit *edit, void *object) 103162306a36Sopenharmony_ci{ 103262306a36Sopenharmony_ci BUG_ON(!object); 103362306a36Sopenharmony_ci edit->leaf = assoc_array_leaf_to_ptr(object); 103462306a36Sopenharmony_ci} 103562306a36Sopenharmony_ci 103662306a36Sopenharmony_cistruct assoc_array_delete_collapse_context { 103762306a36Sopenharmony_ci struct assoc_array_node *node; 103862306a36Sopenharmony_ci const void *skip_leaf; 103962306a36Sopenharmony_ci int slot; 104062306a36Sopenharmony_ci}; 104162306a36Sopenharmony_ci 104262306a36Sopenharmony_ci/* 104362306a36Sopenharmony_ci * Subtree collapse to node iterator. 104462306a36Sopenharmony_ci */ 104562306a36Sopenharmony_cistatic int assoc_array_delete_collapse_iterator(const void *leaf, 104662306a36Sopenharmony_ci void *iterator_data) 104762306a36Sopenharmony_ci{ 104862306a36Sopenharmony_ci struct assoc_array_delete_collapse_context *collapse = iterator_data; 104962306a36Sopenharmony_ci 105062306a36Sopenharmony_ci if (leaf == collapse->skip_leaf) 105162306a36Sopenharmony_ci return 0; 105262306a36Sopenharmony_ci 105362306a36Sopenharmony_ci BUG_ON(collapse->slot >= ASSOC_ARRAY_FAN_OUT); 105462306a36Sopenharmony_ci 105562306a36Sopenharmony_ci collapse->node->slots[collapse->slot++] = assoc_array_leaf_to_ptr(leaf); 105662306a36Sopenharmony_ci return 0; 105762306a36Sopenharmony_ci} 105862306a36Sopenharmony_ci 105962306a36Sopenharmony_ci/** 106062306a36Sopenharmony_ci * assoc_array_delete - Script deletion of an object from an associative array 106162306a36Sopenharmony_ci * @array: The array to search. 106262306a36Sopenharmony_ci * @ops: The operations to use. 106362306a36Sopenharmony_ci * @index_key: The key to the object. 106462306a36Sopenharmony_ci * 106562306a36Sopenharmony_ci * Precalculate and preallocate a script for the deletion of an object from an 106662306a36Sopenharmony_ci * associative array. This results in an edit script that can either be 106762306a36Sopenharmony_ci * applied or cancelled. 106862306a36Sopenharmony_ci * 106962306a36Sopenharmony_ci * The function returns a pointer to an edit script if the object was found, 107062306a36Sopenharmony_ci * NULL if the object was not found or -ENOMEM. 107162306a36Sopenharmony_ci * 107262306a36Sopenharmony_ci * The caller should lock against other modifications and must continue to hold 107362306a36Sopenharmony_ci * the lock until assoc_array_apply_edit() has been called. 107462306a36Sopenharmony_ci * 107562306a36Sopenharmony_ci * Accesses to the tree may take place concurrently with this function, 107662306a36Sopenharmony_ci * provided they hold the RCU read lock. 107762306a36Sopenharmony_ci */ 107862306a36Sopenharmony_cistruct assoc_array_edit *assoc_array_delete(struct assoc_array *array, 107962306a36Sopenharmony_ci const struct assoc_array_ops *ops, 108062306a36Sopenharmony_ci const void *index_key) 108162306a36Sopenharmony_ci{ 108262306a36Sopenharmony_ci struct assoc_array_delete_collapse_context collapse; 108362306a36Sopenharmony_ci struct assoc_array_walk_result result; 108462306a36Sopenharmony_ci struct assoc_array_node *node, *new_n0; 108562306a36Sopenharmony_ci struct assoc_array_edit *edit; 108662306a36Sopenharmony_ci struct assoc_array_ptr *ptr; 108762306a36Sopenharmony_ci bool has_meta; 108862306a36Sopenharmony_ci int slot, i; 108962306a36Sopenharmony_ci 109062306a36Sopenharmony_ci pr_devel("-->%s()\n", __func__); 109162306a36Sopenharmony_ci 109262306a36Sopenharmony_ci edit = kzalloc(sizeof(struct assoc_array_edit), GFP_KERNEL); 109362306a36Sopenharmony_ci if (!edit) 109462306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 109562306a36Sopenharmony_ci edit->array = array; 109662306a36Sopenharmony_ci edit->ops = ops; 109762306a36Sopenharmony_ci edit->adjust_count_by = -1; 109862306a36Sopenharmony_ci 109962306a36Sopenharmony_ci switch (assoc_array_walk(array, ops, index_key, &result)) { 110062306a36Sopenharmony_ci case assoc_array_walk_found_terminal_node: 110162306a36Sopenharmony_ci /* We found a node that should contain the leaf we've been 110262306a36Sopenharmony_ci * asked to remove - *if* it's in the tree. 110362306a36Sopenharmony_ci */ 110462306a36Sopenharmony_ci pr_devel("terminal_node\n"); 110562306a36Sopenharmony_ci node = result.terminal_node.node; 110662306a36Sopenharmony_ci 110762306a36Sopenharmony_ci for (slot = 0; slot < ASSOC_ARRAY_FAN_OUT; slot++) { 110862306a36Sopenharmony_ci ptr = node->slots[slot]; 110962306a36Sopenharmony_ci if (ptr && 111062306a36Sopenharmony_ci assoc_array_ptr_is_leaf(ptr) && 111162306a36Sopenharmony_ci ops->compare_object(assoc_array_ptr_to_leaf(ptr), 111262306a36Sopenharmony_ci index_key)) 111362306a36Sopenharmony_ci goto found_leaf; 111462306a36Sopenharmony_ci } 111562306a36Sopenharmony_ci fallthrough; 111662306a36Sopenharmony_ci case assoc_array_walk_tree_empty: 111762306a36Sopenharmony_ci case assoc_array_walk_found_wrong_shortcut: 111862306a36Sopenharmony_ci default: 111962306a36Sopenharmony_ci assoc_array_cancel_edit(edit); 112062306a36Sopenharmony_ci pr_devel("not found\n"); 112162306a36Sopenharmony_ci return NULL; 112262306a36Sopenharmony_ci } 112362306a36Sopenharmony_ci 112462306a36Sopenharmony_cifound_leaf: 112562306a36Sopenharmony_ci BUG_ON(array->nr_leaves_on_tree <= 0); 112662306a36Sopenharmony_ci 112762306a36Sopenharmony_ci /* In the simplest form of deletion we just clear the slot and release 112862306a36Sopenharmony_ci * the leaf after a suitable interval. 112962306a36Sopenharmony_ci */ 113062306a36Sopenharmony_ci edit->dead_leaf = node->slots[slot]; 113162306a36Sopenharmony_ci edit->set[0].ptr = &node->slots[slot]; 113262306a36Sopenharmony_ci edit->set[0].to = NULL; 113362306a36Sopenharmony_ci edit->adjust_count_on = node; 113462306a36Sopenharmony_ci 113562306a36Sopenharmony_ci /* If that concludes erasure of the last leaf, then delete the entire 113662306a36Sopenharmony_ci * internal array. 113762306a36Sopenharmony_ci */ 113862306a36Sopenharmony_ci if (array->nr_leaves_on_tree == 1) { 113962306a36Sopenharmony_ci edit->set[1].ptr = &array->root; 114062306a36Sopenharmony_ci edit->set[1].to = NULL; 114162306a36Sopenharmony_ci edit->adjust_count_on = NULL; 114262306a36Sopenharmony_ci edit->excised_subtree = array->root; 114362306a36Sopenharmony_ci pr_devel("all gone\n"); 114462306a36Sopenharmony_ci return edit; 114562306a36Sopenharmony_ci } 114662306a36Sopenharmony_ci 114762306a36Sopenharmony_ci /* However, we'd also like to clear up some metadata blocks if we 114862306a36Sopenharmony_ci * possibly can. 114962306a36Sopenharmony_ci * 115062306a36Sopenharmony_ci * We go for a simple algorithm of: if this node has FAN_OUT or fewer 115162306a36Sopenharmony_ci * leaves in it, then attempt to collapse it - and attempt to 115262306a36Sopenharmony_ci * recursively collapse up the tree. 115362306a36Sopenharmony_ci * 115462306a36Sopenharmony_ci * We could also try and collapse in partially filled subtrees to take 115562306a36Sopenharmony_ci * up space in this node. 115662306a36Sopenharmony_ci */ 115762306a36Sopenharmony_ci if (node->nr_leaves_on_branch <= ASSOC_ARRAY_FAN_OUT + 1) { 115862306a36Sopenharmony_ci struct assoc_array_node *parent, *grandparent; 115962306a36Sopenharmony_ci struct assoc_array_ptr *ptr; 116062306a36Sopenharmony_ci 116162306a36Sopenharmony_ci /* First of all, we need to know if this node has metadata so 116262306a36Sopenharmony_ci * that we don't try collapsing if all the leaves are already 116362306a36Sopenharmony_ci * here. 116462306a36Sopenharmony_ci */ 116562306a36Sopenharmony_ci has_meta = false; 116662306a36Sopenharmony_ci for (i = 0; i < ASSOC_ARRAY_FAN_OUT; i++) { 116762306a36Sopenharmony_ci ptr = node->slots[i]; 116862306a36Sopenharmony_ci if (assoc_array_ptr_is_meta(ptr)) { 116962306a36Sopenharmony_ci has_meta = true; 117062306a36Sopenharmony_ci break; 117162306a36Sopenharmony_ci } 117262306a36Sopenharmony_ci } 117362306a36Sopenharmony_ci 117462306a36Sopenharmony_ci pr_devel("leaves: %ld [m=%d]\n", 117562306a36Sopenharmony_ci node->nr_leaves_on_branch - 1, has_meta); 117662306a36Sopenharmony_ci 117762306a36Sopenharmony_ci /* Look further up the tree to see if we can collapse this node 117862306a36Sopenharmony_ci * into a more proximal node too. 117962306a36Sopenharmony_ci */ 118062306a36Sopenharmony_ci parent = node; 118162306a36Sopenharmony_ci collapse_up: 118262306a36Sopenharmony_ci pr_devel("collapse subtree: %ld\n", parent->nr_leaves_on_branch); 118362306a36Sopenharmony_ci 118462306a36Sopenharmony_ci ptr = parent->back_pointer; 118562306a36Sopenharmony_ci if (!ptr) 118662306a36Sopenharmony_ci goto do_collapse; 118762306a36Sopenharmony_ci if (assoc_array_ptr_is_shortcut(ptr)) { 118862306a36Sopenharmony_ci struct assoc_array_shortcut *s = assoc_array_ptr_to_shortcut(ptr); 118962306a36Sopenharmony_ci ptr = s->back_pointer; 119062306a36Sopenharmony_ci if (!ptr) 119162306a36Sopenharmony_ci goto do_collapse; 119262306a36Sopenharmony_ci } 119362306a36Sopenharmony_ci 119462306a36Sopenharmony_ci grandparent = assoc_array_ptr_to_node(ptr); 119562306a36Sopenharmony_ci if (grandparent->nr_leaves_on_branch <= ASSOC_ARRAY_FAN_OUT + 1) { 119662306a36Sopenharmony_ci parent = grandparent; 119762306a36Sopenharmony_ci goto collapse_up; 119862306a36Sopenharmony_ci } 119962306a36Sopenharmony_ci 120062306a36Sopenharmony_ci do_collapse: 120162306a36Sopenharmony_ci /* There's no point collapsing if the original node has no meta 120262306a36Sopenharmony_ci * pointers to discard and if we didn't merge into one of that 120362306a36Sopenharmony_ci * node's ancestry. 120462306a36Sopenharmony_ci */ 120562306a36Sopenharmony_ci if (has_meta || parent != node) { 120662306a36Sopenharmony_ci node = parent; 120762306a36Sopenharmony_ci 120862306a36Sopenharmony_ci /* Create a new node to collapse into */ 120962306a36Sopenharmony_ci new_n0 = kzalloc(sizeof(struct assoc_array_node), GFP_KERNEL); 121062306a36Sopenharmony_ci if (!new_n0) 121162306a36Sopenharmony_ci goto enomem; 121262306a36Sopenharmony_ci edit->new_meta[0] = assoc_array_node_to_ptr(new_n0); 121362306a36Sopenharmony_ci 121462306a36Sopenharmony_ci new_n0->back_pointer = node->back_pointer; 121562306a36Sopenharmony_ci new_n0->parent_slot = node->parent_slot; 121662306a36Sopenharmony_ci new_n0->nr_leaves_on_branch = node->nr_leaves_on_branch; 121762306a36Sopenharmony_ci edit->adjust_count_on = new_n0; 121862306a36Sopenharmony_ci 121962306a36Sopenharmony_ci collapse.node = new_n0; 122062306a36Sopenharmony_ci collapse.skip_leaf = assoc_array_ptr_to_leaf(edit->dead_leaf); 122162306a36Sopenharmony_ci collapse.slot = 0; 122262306a36Sopenharmony_ci assoc_array_subtree_iterate(assoc_array_node_to_ptr(node), 122362306a36Sopenharmony_ci node->back_pointer, 122462306a36Sopenharmony_ci assoc_array_delete_collapse_iterator, 122562306a36Sopenharmony_ci &collapse); 122662306a36Sopenharmony_ci pr_devel("collapsed %d,%lu\n", collapse.slot, new_n0->nr_leaves_on_branch); 122762306a36Sopenharmony_ci BUG_ON(collapse.slot != new_n0->nr_leaves_on_branch - 1); 122862306a36Sopenharmony_ci 122962306a36Sopenharmony_ci if (!node->back_pointer) { 123062306a36Sopenharmony_ci edit->set[1].ptr = &array->root; 123162306a36Sopenharmony_ci } else if (assoc_array_ptr_is_leaf(node->back_pointer)) { 123262306a36Sopenharmony_ci BUG(); 123362306a36Sopenharmony_ci } else if (assoc_array_ptr_is_node(node->back_pointer)) { 123462306a36Sopenharmony_ci struct assoc_array_node *p = 123562306a36Sopenharmony_ci assoc_array_ptr_to_node(node->back_pointer); 123662306a36Sopenharmony_ci edit->set[1].ptr = &p->slots[node->parent_slot]; 123762306a36Sopenharmony_ci } else if (assoc_array_ptr_is_shortcut(node->back_pointer)) { 123862306a36Sopenharmony_ci struct assoc_array_shortcut *s = 123962306a36Sopenharmony_ci assoc_array_ptr_to_shortcut(node->back_pointer); 124062306a36Sopenharmony_ci edit->set[1].ptr = &s->next_node; 124162306a36Sopenharmony_ci } 124262306a36Sopenharmony_ci edit->set[1].to = assoc_array_node_to_ptr(new_n0); 124362306a36Sopenharmony_ci edit->excised_subtree = assoc_array_node_to_ptr(node); 124462306a36Sopenharmony_ci } 124562306a36Sopenharmony_ci } 124662306a36Sopenharmony_ci 124762306a36Sopenharmony_ci return edit; 124862306a36Sopenharmony_ci 124962306a36Sopenharmony_cienomem: 125062306a36Sopenharmony_ci /* Clean up after an out of memory error */ 125162306a36Sopenharmony_ci pr_devel("enomem\n"); 125262306a36Sopenharmony_ci assoc_array_cancel_edit(edit); 125362306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 125462306a36Sopenharmony_ci} 125562306a36Sopenharmony_ci 125662306a36Sopenharmony_ci/** 125762306a36Sopenharmony_ci * assoc_array_clear - Script deletion of all objects from an associative array 125862306a36Sopenharmony_ci * @array: The array to clear. 125962306a36Sopenharmony_ci * @ops: The operations to use. 126062306a36Sopenharmony_ci * 126162306a36Sopenharmony_ci * Precalculate and preallocate a script for the deletion of all the objects 126262306a36Sopenharmony_ci * from an associative array. This results in an edit script that can either 126362306a36Sopenharmony_ci * be applied or cancelled. 126462306a36Sopenharmony_ci * 126562306a36Sopenharmony_ci * The function returns a pointer to an edit script if there are objects to be 126662306a36Sopenharmony_ci * deleted, NULL if there are no objects in the array or -ENOMEM. 126762306a36Sopenharmony_ci * 126862306a36Sopenharmony_ci * The caller should lock against other modifications and must continue to hold 126962306a36Sopenharmony_ci * the lock until assoc_array_apply_edit() has been called. 127062306a36Sopenharmony_ci * 127162306a36Sopenharmony_ci * Accesses to the tree may take place concurrently with this function, 127262306a36Sopenharmony_ci * provided they hold the RCU read lock. 127362306a36Sopenharmony_ci */ 127462306a36Sopenharmony_cistruct assoc_array_edit *assoc_array_clear(struct assoc_array *array, 127562306a36Sopenharmony_ci const struct assoc_array_ops *ops) 127662306a36Sopenharmony_ci{ 127762306a36Sopenharmony_ci struct assoc_array_edit *edit; 127862306a36Sopenharmony_ci 127962306a36Sopenharmony_ci pr_devel("-->%s()\n", __func__); 128062306a36Sopenharmony_ci 128162306a36Sopenharmony_ci if (!array->root) 128262306a36Sopenharmony_ci return NULL; 128362306a36Sopenharmony_ci 128462306a36Sopenharmony_ci edit = kzalloc(sizeof(struct assoc_array_edit), GFP_KERNEL); 128562306a36Sopenharmony_ci if (!edit) 128662306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 128762306a36Sopenharmony_ci edit->array = array; 128862306a36Sopenharmony_ci edit->ops = ops; 128962306a36Sopenharmony_ci edit->set[1].ptr = &array->root; 129062306a36Sopenharmony_ci edit->set[1].to = NULL; 129162306a36Sopenharmony_ci edit->excised_subtree = array->root; 129262306a36Sopenharmony_ci edit->ops_for_excised_subtree = ops; 129362306a36Sopenharmony_ci pr_devel("all gone\n"); 129462306a36Sopenharmony_ci return edit; 129562306a36Sopenharmony_ci} 129662306a36Sopenharmony_ci 129762306a36Sopenharmony_ci/* 129862306a36Sopenharmony_ci * Handle the deferred destruction after an applied edit. 129962306a36Sopenharmony_ci */ 130062306a36Sopenharmony_cistatic void assoc_array_rcu_cleanup(struct rcu_head *head) 130162306a36Sopenharmony_ci{ 130262306a36Sopenharmony_ci struct assoc_array_edit *edit = 130362306a36Sopenharmony_ci container_of(head, struct assoc_array_edit, rcu); 130462306a36Sopenharmony_ci int i; 130562306a36Sopenharmony_ci 130662306a36Sopenharmony_ci pr_devel("-->%s()\n", __func__); 130762306a36Sopenharmony_ci 130862306a36Sopenharmony_ci if (edit->dead_leaf) 130962306a36Sopenharmony_ci edit->ops->free_object(assoc_array_ptr_to_leaf(edit->dead_leaf)); 131062306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(edit->excised_meta); i++) 131162306a36Sopenharmony_ci if (edit->excised_meta[i]) 131262306a36Sopenharmony_ci kfree(assoc_array_ptr_to_node(edit->excised_meta[i])); 131362306a36Sopenharmony_ci 131462306a36Sopenharmony_ci if (edit->excised_subtree) { 131562306a36Sopenharmony_ci BUG_ON(assoc_array_ptr_is_leaf(edit->excised_subtree)); 131662306a36Sopenharmony_ci if (assoc_array_ptr_is_node(edit->excised_subtree)) { 131762306a36Sopenharmony_ci struct assoc_array_node *n = 131862306a36Sopenharmony_ci assoc_array_ptr_to_node(edit->excised_subtree); 131962306a36Sopenharmony_ci n->back_pointer = NULL; 132062306a36Sopenharmony_ci } else { 132162306a36Sopenharmony_ci struct assoc_array_shortcut *s = 132262306a36Sopenharmony_ci assoc_array_ptr_to_shortcut(edit->excised_subtree); 132362306a36Sopenharmony_ci s->back_pointer = NULL; 132462306a36Sopenharmony_ci } 132562306a36Sopenharmony_ci assoc_array_destroy_subtree(edit->excised_subtree, 132662306a36Sopenharmony_ci edit->ops_for_excised_subtree); 132762306a36Sopenharmony_ci } 132862306a36Sopenharmony_ci 132962306a36Sopenharmony_ci kfree(edit); 133062306a36Sopenharmony_ci} 133162306a36Sopenharmony_ci 133262306a36Sopenharmony_ci/** 133362306a36Sopenharmony_ci * assoc_array_apply_edit - Apply an edit script to an associative array 133462306a36Sopenharmony_ci * @edit: The script to apply. 133562306a36Sopenharmony_ci * 133662306a36Sopenharmony_ci * Apply an edit script to an associative array to effect an insertion, 133762306a36Sopenharmony_ci * deletion or clearance. As the edit script includes preallocated memory, 133862306a36Sopenharmony_ci * this is guaranteed not to fail. 133962306a36Sopenharmony_ci * 134062306a36Sopenharmony_ci * The edit script, dead objects and dead metadata will be scheduled for 134162306a36Sopenharmony_ci * destruction after an RCU grace period to permit those doing read-only 134262306a36Sopenharmony_ci * accesses on the array to continue to do so under the RCU read lock whilst 134362306a36Sopenharmony_ci * the edit is taking place. 134462306a36Sopenharmony_ci */ 134562306a36Sopenharmony_civoid assoc_array_apply_edit(struct assoc_array_edit *edit) 134662306a36Sopenharmony_ci{ 134762306a36Sopenharmony_ci struct assoc_array_shortcut *shortcut; 134862306a36Sopenharmony_ci struct assoc_array_node *node; 134962306a36Sopenharmony_ci struct assoc_array_ptr *ptr; 135062306a36Sopenharmony_ci int i; 135162306a36Sopenharmony_ci 135262306a36Sopenharmony_ci pr_devel("-->%s()\n", __func__); 135362306a36Sopenharmony_ci 135462306a36Sopenharmony_ci smp_wmb(); 135562306a36Sopenharmony_ci if (edit->leaf_p) 135662306a36Sopenharmony_ci *edit->leaf_p = edit->leaf; 135762306a36Sopenharmony_ci 135862306a36Sopenharmony_ci smp_wmb(); 135962306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(edit->set_parent_slot); i++) 136062306a36Sopenharmony_ci if (edit->set_parent_slot[i].p) 136162306a36Sopenharmony_ci *edit->set_parent_slot[i].p = edit->set_parent_slot[i].to; 136262306a36Sopenharmony_ci 136362306a36Sopenharmony_ci smp_wmb(); 136462306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(edit->set_backpointers); i++) 136562306a36Sopenharmony_ci if (edit->set_backpointers[i]) 136662306a36Sopenharmony_ci *edit->set_backpointers[i] = edit->set_backpointers_to; 136762306a36Sopenharmony_ci 136862306a36Sopenharmony_ci smp_wmb(); 136962306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(edit->set); i++) 137062306a36Sopenharmony_ci if (edit->set[i].ptr) 137162306a36Sopenharmony_ci *edit->set[i].ptr = edit->set[i].to; 137262306a36Sopenharmony_ci 137362306a36Sopenharmony_ci if (edit->array->root == NULL) { 137462306a36Sopenharmony_ci edit->array->nr_leaves_on_tree = 0; 137562306a36Sopenharmony_ci } else if (edit->adjust_count_on) { 137662306a36Sopenharmony_ci node = edit->adjust_count_on; 137762306a36Sopenharmony_ci for (;;) { 137862306a36Sopenharmony_ci node->nr_leaves_on_branch += edit->adjust_count_by; 137962306a36Sopenharmony_ci 138062306a36Sopenharmony_ci ptr = node->back_pointer; 138162306a36Sopenharmony_ci if (!ptr) 138262306a36Sopenharmony_ci break; 138362306a36Sopenharmony_ci if (assoc_array_ptr_is_shortcut(ptr)) { 138462306a36Sopenharmony_ci shortcut = assoc_array_ptr_to_shortcut(ptr); 138562306a36Sopenharmony_ci ptr = shortcut->back_pointer; 138662306a36Sopenharmony_ci if (!ptr) 138762306a36Sopenharmony_ci break; 138862306a36Sopenharmony_ci } 138962306a36Sopenharmony_ci BUG_ON(!assoc_array_ptr_is_node(ptr)); 139062306a36Sopenharmony_ci node = assoc_array_ptr_to_node(ptr); 139162306a36Sopenharmony_ci } 139262306a36Sopenharmony_ci 139362306a36Sopenharmony_ci edit->array->nr_leaves_on_tree += edit->adjust_count_by; 139462306a36Sopenharmony_ci } 139562306a36Sopenharmony_ci 139662306a36Sopenharmony_ci call_rcu(&edit->rcu, assoc_array_rcu_cleanup); 139762306a36Sopenharmony_ci} 139862306a36Sopenharmony_ci 139962306a36Sopenharmony_ci/** 140062306a36Sopenharmony_ci * assoc_array_cancel_edit - Discard an edit script. 140162306a36Sopenharmony_ci * @edit: The script to discard. 140262306a36Sopenharmony_ci * 140362306a36Sopenharmony_ci * Free an edit script and all the preallocated data it holds without making 140462306a36Sopenharmony_ci * any changes to the associative array it was intended for. 140562306a36Sopenharmony_ci * 140662306a36Sopenharmony_ci * NOTE! In the case of an insertion script, this does _not_ release the leaf 140762306a36Sopenharmony_ci * that was to be inserted. That is left to the caller. 140862306a36Sopenharmony_ci */ 140962306a36Sopenharmony_civoid assoc_array_cancel_edit(struct assoc_array_edit *edit) 141062306a36Sopenharmony_ci{ 141162306a36Sopenharmony_ci struct assoc_array_ptr *ptr; 141262306a36Sopenharmony_ci int i; 141362306a36Sopenharmony_ci 141462306a36Sopenharmony_ci pr_devel("-->%s()\n", __func__); 141562306a36Sopenharmony_ci 141662306a36Sopenharmony_ci /* Clean up after an out of memory error */ 141762306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(edit->new_meta); i++) { 141862306a36Sopenharmony_ci ptr = edit->new_meta[i]; 141962306a36Sopenharmony_ci if (ptr) { 142062306a36Sopenharmony_ci if (assoc_array_ptr_is_node(ptr)) 142162306a36Sopenharmony_ci kfree(assoc_array_ptr_to_node(ptr)); 142262306a36Sopenharmony_ci else 142362306a36Sopenharmony_ci kfree(assoc_array_ptr_to_shortcut(ptr)); 142462306a36Sopenharmony_ci } 142562306a36Sopenharmony_ci } 142662306a36Sopenharmony_ci kfree(edit); 142762306a36Sopenharmony_ci} 142862306a36Sopenharmony_ci 142962306a36Sopenharmony_ci/** 143062306a36Sopenharmony_ci * assoc_array_gc - Garbage collect an associative array. 143162306a36Sopenharmony_ci * @array: The array to clean. 143262306a36Sopenharmony_ci * @ops: The operations to use. 143362306a36Sopenharmony_ci * @iterator: A callback function to pass judgement on each object. 143462306a36Sopenharmony_ci * @iterator_data: Private data for the callback function. 143562306a36Sopenharmony_ci * 143662306a36Sopenharmony_ci * Collect garbage from an associative array and pack down the internal tree to 143762306a36Sopenharmony_ci * save memory. 143862306a36Sopenharmony_ci * 143962306a36Sopenharmony_ci * The iterator function is asked to pass judgement upon each object in the 144062306a36Sopenharmony_ci * array. If it returns false, the object is discard and if it returns true, 144162306a36Sopenharmony_ci * the object is kept. If it returns true, it must increment the object's 144262306a36Sopenharmony_ci * usage count (or whatever it needs to do to retain it) before returning. 144362306a36Sopenharmony_ci * 144462306a36Sopenharmony_ci * This function returns 0 if successful or -ENOMEM if out of memory. In the 144562306a36Sopenharmony_ci * latter case, the array is not changed. 144662306a36Sopenharmony_ci * 144762306a36Sopenharmony_ci * The caller should lock against other modifications and must continue to hold 144862306a36Sopenharmony_ci * the lock until assoc_array_apply_edit() has been called. 144962306a36Sopenharmony_ci * 145062306a36Sopenharmony_ci * Accesses to the tree may take place concurrently with this function, 145162306a36Sopenharmony_ci * provided they hold the RCU read lock. 145262306a36Sopenharmony_ci */ 145362306a36Sopenharmony_ciint assoc_array_gc(struct assoc_array *array, 145462306a36Sopenharmony_ci const struct assoc_array_ops *ops, 145562306a36Sopenharmony_ci bool (*iterator)(void *object, void *iterator_data), 145662306a36Sopenharmony_ci void *iterator_data) 145762306a36Sopenharmony_ci{ 145862306a36Sopenharmony_ci struct assoc_array_shortcut *shortcut, *new_s; 145962306a36Sopenharmony_ci struct assoc_array_node *node, *new_n; 146062306a36Sopenharmony_ci struct assoc_array_edit *edit; 146162306a36Sopenharmony_ci struct assoc_array_ptr *cursor, *ptr; 146262306a36Sopenharmony_ci struct assoc_array_ptr *new_root, *new_parent, **new_ptr_pp; 146362306a36Sopenharmony_ci unsigned long nr_leaves_on_tree; 146462306a36Sopenharmony_ci bool retained; 146562306a36Sopenharmony_ci int keylen, slot, nr_free, next_slot, i; 146662306a36Sopenharmony_ci 146762306a36Sopenharmony_ci pr_devel("-->%s()\n", __func__); 146862306a36Sopenharmony_ci 146962306a36Sopenharmony_ci if (!array->root) 147062306a36Sopenharmony_ci return 0; 147162306a36Sopenharmony_ci 147262306a36Sopenharmony_ci edit = kzalloc(sizeof(struct assoc_array_edit), GFP_KERNEL); 147362306a36Sopenharmony_ci if (!edit) 147462306a36Sopenharmony_ci return -ENOMEM; 147562306a36Sopenharmony_ci edit->array = array; 147662306a36Sopenharmony_ci edit->ops = ops; 147762306a36Sopenharmony_ci edit->ops_for_excised_subtree = ops; 147862306a36Sopenharmony_ci edit->set[0].ptr = &array->root; 147962306a36Sopenharmony_ci edit->excised_subtree = array->root; 148062306a36Sopenharmony_ci 148162306a36Sopenharmony_ci new_root = new_parent = NULL; 148262306a36Sopenharmony_ci new_ptr_pp = &new_root; 148362306a36Sopenharmony_ci cursor = array->root; 148462306a36Sopenharmony_ci 148562306a36Sopenharmony_cidescend: 148662306a36Sopenharmony_ci /* If this point is a shortcut, then we need to duplicate it and 148762306a36Sopenharmony_ci * advance the target cursor. 148862306a36Sopenharmony_ci */ 148962306a36Sopenharmony_ci if (assoc_array_ptr_is_shortcut(cursor)) { 149062306a36Sopenharmony_ci shortcut = assoc_array_ptr_to_shortcut(cursor); 149162306a36Sopenharmony_ci keylen = round_up(shortcut->skip_to_level, ASSOC_ARRAY_KEY_CHUNK_SIZE); 149262306a36Sopenharmony_ci keylen >>= ASSOC_ARRAY_KEY_CHUNK_SHIFT; 149362306a36Sopenharmony_ci new_s = kmalloc(struct_size(new_s, index_key, keylen), 149462306a36Sopenharmony_ci GFP_KERNEL); 149562306a36Sopenharmony_ci if (!new_s) 149662306a36Sopenharmony_ci goto enomem; 149762306a36Sopenharmony_ci pr_devel("dup shortcut %p -> %p\n", shortcut, new_s); 149862306a36Sopenharmony_ci memcpy(new_s, shortcut, struct_size(new_s, index_key, keylen)); 149962306a36Sopenharmony_ci new_s->back_pointer = new_parent; 150062306a36Sopenharmony_ci new_s->parent_slot = shortcut->parent_slot; 150162306a36Sopenharmony_ci *new_ptr_pp = new_parent = assoc_array_shortcut_to_ptr(new_s); 150262306a36Sopenharmony_ci new_ptr_pp = &new_s->next_node; 150362306a36Sopenharmony_ci cursor = shortcut->next_node; 150462306a36Sopenharmony_ci } 150562306a36Sopenharmony_ci 150662306a36Sopenharmony_ci /* Duplicate the node at this position */ 150762306a36Sopenharmony_ci node = assoc_array_ptr_to_node(cursor); 150862306a36Sopenharmony_ci new_n = kzalloc(sizeof(struct assoc_array_node), GFP_KERNEL); 150962306a36Sopenharmony_ci if (!new_n) 151062306a36Sopenharmony_ci goto enomem; 151162306a36Sopenharmony_ci pr_devel("dup node %p -> %p\n", node, new_n); 151262306a36Sopenharmony_ci new_n->back_pointer = new_parent; 151362306a36Sopenharmony_ci new_n->parent_slot = node->parent_slot; 151462306a36Sopenharmony_ci *new_ptr_pp = new_parent = assoc_array_node_to_ptr(new_n); 151562306a36Sopenharmony_ci new_ptr_pp = NULL; 151662306a36Sopenharmony_ci slot = 0; 151762306a36Sopenharmony_ci 151862306a36Sopenharmony_cicontinue_node: 151962306a36Sopenharmony_ci /* Filter across any leaves and gc any subtrees */ 152062306a36Sopenharmony_ci for (; slot < ASSOC_ARRAY_FAN_OUT; slot++) { 152162306a36Sopenharmony_ci ptr = node->slots[slot]; 152262306a36Sopenharmony_ci if (!ptr) 152362306a36Sopenharmony_ci continue; 152462306a36Sopenharmony_ci 152562306a36Sopenharmony_ci if (assoc_array_ptr_is_leaf(ptr)) { 152662306a36Sopenharmony_ci if (iterator(assoc_array_ptr_to_leaf(ptr), 152762306a36Sopenharmony_ci iterator_data)) 152862306a36Sopenharmony_ci /* The iterator will have done any reference 152962306a36Sopenharmony_ci * counting on the object for us. 153062306a36Sopenharmony_ci */ 153162306a36Sopenharmony_ci new_n->slots[slot] = ptr; 153262306a36Sopenharmony_ci continue; 153362306a36Sopenharmony_ci } 153462306a36Sopenharmony_ci 153562306a36Sopenharmony_ci new_ptr_pp = &new_n->slots[slot]; 153662306a36Sopenharmony_ci cursor = ptr; 153762306a36Sopenharmony_ci goto descend; 153862306a36Sopenharmony_ci } 153962306a36Sopenharmony_ci 154062306a36Sopenharmony_ciretry_compress: 154162306a36Sopenharmony_ci pr_devel("-- compress node %p --\n", new_n); 154262306a36Sopenharmony_ci 154362306a36Sopenharmony_ci /* Count up the number of empty slots in this node and work out the 154462306a36Sopenharmony_ci * subtree leaf count. 154562306a36Sopenharmony_ci */ 154662306a36Sopenharmony_ci new_n->nr_leaves_on_branch = 0; 154762306a36Sopenharmony_ci nr_free = 0; 154862306a36Sopenharmony_ci for (slot = 0; slot < ASSOC_ARRAY_FAN_OUT; slot++) { 154962306a36Sopenharmony_ci ptr = new_n->slots[slot]; 155062306a36Sopenharmony_ci if (!ptr) 155162306a36Sopenharmony_ci nr_free++; 155262306a36Sopenharmony_ci else if (assoc_array_ptr_is_leaf(ptr)) 155362306a36Sopenharmony_ci new_n->nr_leaves_on_branch++; 155462306a36Sopenharmony_ci } 155562306a36Sopenharmony_ci pr_devel("free=%d, leaves=%lu\n", nr_free, new_n->nr_leaves_on_branch); 155662306a36Sopenharmony_ci 155762306a36Sopenharmony_ci /* See what we can fold in */ 155862306a36Sopenharmony_ci retained = false; 155962306a36Sopenharmony_ci next_slot = 0; 156062306a36Sopenharmony_ci for (slot = 0; slot < ASSOC_ARRAY_FAN_OUT; slot++) { 156162306a36Sopenharmony_ci struct assoc_array_shortcut *s; 156262306a36Sopenharmony_ci struct assoc_array_node *child; 156362306a36Sopenharmony_ci 156462306a36Sopenharmony_ci ptr = new_n->slots[slot]; 156562306a36Sopenharmony_ci if (!ptr || assoc_array_ptr_is_leaf(ptr)) 156662306a36Sopenharmony_ci continue; 156762306a36Sopenharmony_ci 156862306a36Sopenharmony_ci s = NULL; 156962306a36Sopenharmony_ci if (assoc_array_ptr_is_shortcut(ptr)) { 157062306a36Sopenharmony_ci s = assoc_array_ptr_to_shortcut(ptr); 157162306a36Sopenharmony_ci ptr = s->next_node; 157262306a36Sopenharmony_ci } 157362306a36Sopenharmony_ci 157462306a36Sopenharmony_ci child = assoc_array_ptr_to_node(ptr); 157562306a36Sopenharmony_ci new_n->nr_leaves_on_branch += child->nr_leaves_on_branch; 157662306a36Sopenharmony_ci 157762306a36Sopenharmony_ci if (child->nr_leaves_on_branch <= nr_free + 1) { 157862306a36Sopenharmony_ci /* Fold the child node into this one */ 157962306a36Sopenharmony_ci pr_devel("[%d] fold node %lu/%d [nx %d]\n", 158062306a36Sopenharmony_ci slot, child->nr_leaves_on_branch, nr_free + 1, 158162306a36Sopenharmony_ci next_slot); 158262306a36Sopenharmony_ci 158362306a36Sopenharmony_ci /* We would already have reaped an intervening shortcut 158462306a36Sopenharmony_ci * on the way back up the tree. 158562306a36Sopenharmony_ci */ 158662306a36Sopenharmony_ci BUG_ON(s); 158762306a36Sopenharmony_ci 158862306a36Sopenharmony_ci new_n->slots[slot] = NULL; 158962306a36Sopenharmony_ci nr_free++; 159062306a36Sopenharmony_ci if (slot < next_slot) 159162306a36Sopenharmony_ci next_slot = slot; 159262306a36Sopenharmony_ci for (i = 0; i < ASSOC_ARRAY_FAN_OUT; i++) { 159362306a36Sopenharmony_ci struct assoc_array_ptr *p = child->slots[i]; 159462306a36Sopenharmony_ci if (!p) 159562306a36Sopenharmony_ci continue; 159662306a36Sopenharmony_ci BUG_ON(assoc_array_ptr_is_meta(p)); 159762306a36Sopenharmony_ci while (new_n->slots[next_slot]) 159862306a36Sopenharmony_ci next_slot++; 159962306a36Sopenharmony_ci BUG_ON(next_slot >= ASSOC_ARRAY_FAN_OUT); 160062306a36Sopenharmony_ci new_n->slots[next_slot++] = p; 160162306a36Sopenharmony_ci nr_free--; 160262306a36Sopenharmony_ci } 160362306a36Sopenharmony_ci kfree(child); 160462306a36Sopenharmony_ci } else { 160562306a36Sopenharmony_ci pr_devel("[%d] retain node %lu/%d [nx %d]\n", 160662306a36Sopenharmony_ci slot, child->nr_leaves_on_branch, nr_free + 1, 160762306a36Sopenharmony_ci next_slot); 160862306a36Sopenharmony_ci retained = true; 160962306a36Sopenharmony_ci } 161062306a36Sopenharmony_ci } 161162306a36Sopenharmony_ci 161262306a36Sopenharmony_ci if (retained && new_n->nr_leaves_on_branch <= ASSOC_ARRAY_FAN_OUT) { 161362306a36Sopenharmony_ci pr_devel("internal nodes remain despite enough space, retrying\n"); 161462306a36Sopenharmony_ci goto retry_compress; 161562306a36Sopenharmony_ci } 161662306a36Sopenharmony_ci pr_devel("after: %lu\n", new_n->nr_leaves_on_branch); 161762306a36Sopenharmony_ci 161862306a36Sopenharmony_ci nr_leaves_on_tree = new_n->nr_leaves_on_branch; 161962306a36Sopenharmony_ci 162062306a36Sopenharmony_ci /* Excise this node if it is singly occupied by a shortcut */ 162162306a36Sopenharmony_ci if (nr_free == ASSOC_ARRAY_FAN_OUT - 1) { 162262306a36Sopenharmony_ci for (slot = 0; slot < ASSOC_ARRAY_FAN_OUT; slot++) 162362306a36Sopenharmony_ci if ((ptr = new_n->slots[slot])) 162462306a36Sopenharmony_ci break; 162562306a36Sopenharmony_ci 162662306a36Sopenharmony_ci if (assoc_array_ptr_is_meta(ptr) && 162762306a36Sopenharmony_ci assoc_array_ptr_is_shortcut(ptr)) { 162862306a36Sopenharmony_ci pr_devel("excise node %p with 1 shortcut\n", new_n); 162962306a36Sopenharmony_ci new_s = assoc_array_ptr_to_shortcut(ptr); 163062306a36Sopenharmony_ci new_parent = new_n->back_pointer; 163162306a36Sopenharmony_ci slot = new_n->parent_slot; 163262306a36Sopenharmony_ci kfree(new_n); 163362306a36Sopenharmony_ci if (!new_parent) { 163462306a36Sopenharmony_ci new_s->back_pointer = NULL; 163562306a36Sopenharmony_ci new_s->parent_slot = 0; 163662306a36Sopenharmony_ci new_root = ptr; 163762306a36Sopenharmony_ci goto gc_complete; 163862306a36Sopenharmony_ci } 163962306a36Sopenharmony_ci 164062306a36Sopenharmony_ci if (assoc_array_ptr_is_shortcut(new_parent)) { 164162306a36Sopenharmony_ci /* We can discard any preceding shortcut also */ 164262306a36Sopenharmony_ci struct assoc_array_shortcut *s = 164362306a36Sopenharmony_ci assoc_array_ptr_to_shortcut(new_parent); 164462306a36Sopenharmony_ci 164562306a36Sopenharmony_ci pr_devel("excise preceding shortcut\n"); 164662306a36Sopenharmony_ci 164762306a36Sopenharmony_ci new_parent = new_s->back_pointer = s->back_pointer; 164862306a36Sopenharmony_ci slot = new_s->parent_slot = s->parent_slot; 164962306a36Sopenharmony_ci kfree(s); 165062306a36Sopenharmony_ci if (!new_parent) { 165162306a36Sopenharmony_ci new_s->back_pointer = NULL; 165262306a36Sopenharmony_ci new_s->parent_slot = 0; 165362306a36Sopenharmony_ci new_root = ptr; 165462306a36Sopenharmony_ci goto gc_complete; 165562306a36Sopenharmony_ci } 165662306a36Sopenharmony_ci } 165762306a36Sopenharmony_ci 165862306a36Sopenharmony_ci new_s->back_pointer = new_parent; 165962306a36Sopenharmony_ci new_s->parent_slot = slot; 166062306a36Sopenharmony_ci new_n = assoc_array_ptr_to_node(new_parent); 166162306a36Sopenharmony_ci new_n->slots[slot] = ptr; 166262306a36Sopenharmony_ci goto ascend_old_tree; 166362306a36Sopenharmony_ci } 166462306a36Sopenharmony_ci } 166562306a36Sopenharmony_ci 166662306a36Sopenharmony_ci /* Excise any shortcuts we might encounter that point to nodes that 166762306a36Sopenharmony_ci * only contain leaves. 166862306a36Sopenharmony_ci */ 166962306a36Sopenharmony_ci ptr = new_n->back_pointer; 167062306a36Sopenharmony_ci if (!ptr) 167162306a36Sopenharmony_ci goto gc_complete; 167262306a36Sopenharmony_ci 167362306a36Sopenharmony_ci if (assoc_array_ptr_is_shortcut(ptr)) { 167462306a36Sopenharmony_ci new_s = assoc_array_ptr_to_shortcut(ptr); 167562306a36Sopenharmony_ci new_parent = new_s->back_pointer; 167662306a36Sopenharmony_ci slot = new_s->parent_slot; 167762306a36Sopenharmony_ci 167862306a36Sopenharmony_ci if (new_n->nr_leaves_on_branch <= ASSOC_ARRAY_FAN_OUT) { 167962306a36Sopenharmony_ci struct assoc_array_node *n; 168062306a36Sopenharmony_ci 168162306a36Sopenharmony_ci pr_devel("excise shortcut\n"); 168262306a36Sopenharmony_ci new_n->back_pointer = new_parent; 168362306a36Sopenharmony_ci new_n->parent_slot = slot; 168462306a36Sopenharmony_ci kfree(new_s); 168562306a36Sopenharmony_ci if (!new_parent) { 168662306a36Sopenharmony_ci new_root = assoc_array_node_to_ptr(new_n); 168762306a36Sopenharmony_ci goto gc_complete; 168862306a36Sopenharmony_ci } 168962306a36Sopenharmony_ci 169062306a36Sopenharmony_ci n = assoc_array_ptr_to_node(new_parent); 169162306a36Sopenharmony_ci n->slots[slot] = assoc_array_node_to_ptr(new_n); 169262306a36Sopenharmony_ci } 169362306a36Sopenharmony_ci } else { 169462306a36Sopenharmony_ci new_parent = ptr; 169562306a36Sopenharmony_ci } 169662306a36Sopenharmony_ci new_n = assoc_array_ptr_to_node(new_parent); 169762306a36Sopenharmony_ci 169862306a36Sopenharmony_ciascend_old_tree: 169962306a36Sopenharmony_ci ptr = node->back_pointer; 170062306a36Sopenharmony_ci if (assoc_array_ptr_is_shortcut(ptr)) { 170162306a36Sopenharmony_ci shortcut = assoc_array_ptr_to_shortcut(ptr); 170262306a36Sopenharmony_ci slot = shortcut->parent_slot; 170362306a36Sopenharmony_ci cursor = shortcut->back_pointer; 170462306a36Sopenharmony_ci if (!cursor) 170562306a36Sopenharmony_ci goto gc_complete; 170662306a36Sopenharmony_ci } else { 170762306a36Sopenharmony_ci slot = node->parent_slot; 170862306a36Sopenharmony_ci cursor = ptr; 170962306a36Sopenharmony_ci } 171062306a36Sopenharmony_ci BUG_ON(!cursor); 171162306a36Sopenharmony_ci node = assoc_array_ptr_to_node(cursor); 171262306a36Sopenharmony_ci slot++; 171362306a36Sopenharmony_ci goto continue_node; 171462306a36Sopenharmony_ci 171562306a36Sopenharmony_cigc_complete: 171662306a36Sopenharmony_ci edit->set[0].to = new_root; 171762306a36Sopenharmony_ci assoc_array_apply_edit(edit); 171862306a36Sopenharmony_ci array->nr_leaves_on_tree = nr_leaves_on_tree; 171962306a36Sopenharmony_ci return 0; 172062306a36Sopenharmony_ci 172162306a36Sopenharmony_cienomem: 172262306a36Sopenharmony_ci pr_devel("enomem\n"); 172362306a36Sopenharmony_ci assoc_array_destroy_subtree(new_root, edit->ops); 172462306a36Sopenharmony_ci kfree(edit); 172562306a36Sopenharmony_ci return -ENOMEM; 172662306a36Sopenharmony_ci} 1727