1b4d0d230SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 23cb98950SDavid Howells /* Generic associative array implementation. 33cb98950SDavid Howells * 448c40c26SAlexander Kuleshov * See Documentation/core-api/assoc_array.rst for information. 53cb98950SDavid Howells * 63cb98950SDavid Howells * Copyright (C) 2013 Red Hat, Inc. All Rights Reserved. 73cb98950SDavid Howells * Written by David Howells (dhowells@redhat.com) 83cb98950SDavid Howells */ 93cb98950SDavid Howells //#define DEBUG 10990428b8SPranith Kumar #include <linux/rcupdate.h> 113cb98950SDavid Howells #include <linux/slab.h> 12b2a4df20SDavid Howells #include <linux/err.h> 133cb98950SDavid Howells #include <linux/assoc_array_priv.h> 143cb98950SDavid Howells 153cb98950SDavid Howells /* 163cb98950SDavid Howells * Iterate over an associative array. The caller must hold the RCU read lock 173cb98950SDavid Howells * or better. 183cb98950SDavid Howells */ 193cb98950SDavid Howells static int assoc_array_subtree_iterate(const struct assoc_array_ptr *root, 203cb98950SDavid Howells const struct assoc_array_ptr *stop, 213cb98950SDavid Howells int (*iterator)(const void *leaf, 223cb98950SDavid Howells void *iterator_data), 233cb98950SDavid Howells void *iterator_data) 243cb98950SDavid Howells { 253cb98950SDavid Howells const struct assoc_array_shortcut *shortcut; 263cb98950SDavid Howells const struct assoc_array_node *node; 273cb98950SDavid Howells const struct assoc_array_ptr *cursor, *ptr, *parent; 283cb98950SDavid Howells unsigned long has_meta; 293cb98950SDavid Howells int slot, ret; 303cb98950SDavid Howells 313cb98950SDavid Howells cursor = root; 323cb98950SDavid Howells 333cb98950SDavid Howells begin_node: 343cb98950SDavid Howells if (assoc_array_ptr_is_shortcut(cursor)) { 353cb98950SDavid Howells /* Descend through a shortcut */ 363cb98950SDavid Howells shortcut = assoc_array_ptr_to_shortcut(cursor); 37516df050SPaul E. McKenney cursor = READ_ONCE(shortcut->next_node); /* Address dependency. */ 383cb98950SDavid Howells } 393cb98950SDavid Howells 403cb98950SDavid Howells node = assoc_array_ptr_to_node(cursor); 413cb98950SDavid Howells slot = 0; 423cb98950SDavid Howells 433cb98950SDavid Howells /* We perform two passes of each node. 443cb98950SDavid Howells * 453cb98950SDavid Howells * The first pass does all the leaves in this node. This means we 463cb98950SDavid Howells * don't miss any leaves if the node is split up by insertion whilst 473cb98950SDavid Howells * we're iterating over the branches rooted here (we may, however, see 483cb98950SDavid Howells * some leaves twice). 493cb98950SDavid Howells */ 503cb98950SDavid Howells has_meta = 0; 513cb98950SDavid Howells for (; slot < ASSOC_ARRAY_FAN_OUT; slot++) { 52516df050SPaul E. McKenney ptr = READ_ONCE(node->slots[slot]); /* Address dependency. */ 533cb98950SDavid Howells has_meta |= (unsigned long)ptr; 543cb98950SDavid Howells if (ptr && assoc_array_ptr_is_leaf(ptr)) { 55516df050SPaul E. McKenney /* We need a barrier between the read of the pointer, 56516df050SPaul E. McKenney * which is supplied by the above READ_ONCE(). 573cb98950SDavid Howells */ 583cb98950SDavid Howells /* Invoke the callback */ 593cb98950SDavid Howells ret = iterator(assoc_array_ptr_to_leaf(ptr), 603cb98950SDavid Howells iterator_data); 613cb98950SDavid Howells if (ret) 623cb98950SDavid Howells return ret; 633cb98950SDavid Howells } 643cb98950SDavid Howells } 653cb98950SDavid Howells 663cb98950SDavid Howells /* The second pass attends to all the metadata pointers. If we follow 673cb98950SDavid Howells * one of these we may find that we don't come back here, but rather go 683cb98950SDavid Howells * back to a replacement node with the leaves in a different layout. 693cb98950SDavid Howells * 703cb98950SDavid Howells * We are guaranteed to make progress, however, as the slot number for 713cb98950SDavid Howells * a particular portion of the key space cannot change - and we 723cb98950SDavid Howells * continue at the back pointer + 1. 733cb98950SDavid Howells */ 743cb98950SDavid Howells if (!(has_meta & ASSOC_ARRAY_PTR_META_TYPE)) 753cb98950SDavid Howells goto finished_node; 763cb98950SDavid Howells slot = 0; 773cb98950SDavid Howells 783cb98950SDavid Howells continue_node: 793cb98950SDavid Howells node = assoc_array_ptr_to_node(cursor); 803cb98950SDavid Howells for (; slot < ASSOC_ARRAY_FAN_OUT; slot++) { 81516df050SPaul E. McKenney ptr = READ_ONCE(node->slots[slot]); /* Address dependency. */ 823cb98950SDavid Howells if (assoc_array_ptr_is_meta(ptr)) { 833cb98950SDavid Howells cursor = ptr; 843cb98950SDavid Howells goto begin_node; 853cb98950SDavid Howells } 863cb98950SDavid Howells } 873cb98950SDavid Howells 883cb98950SDavid Howells finished_node: 893cb98950SDavid Howells /* Move up to the parent (may need to skip back over a shortcut) */ 90516df050SPaul E. McKenney parent = READ_ONCE(node->back_pointer); /* Address dependency. */ 913cb98950SDavid Howells slot = node->parent_slot; 923cb98950SDavid Howells if (parent == stop) 933cb98950SDavid Howells return 0; 943cb98950SDavid Howells 953cb98950SDavid Howells if (assoc_array_ptr_is_shortcut(parent)) { 963cb98950SDavid Howells shortcut = assoc_array_ptr_to_shortcut(parent); 973cb98950SDavid Howells cursor = parent; 98516df050SPaul E. McKenney parent = READ_ONCE(shortcut->back_pointer); /* Address dependency. */ 993cb98950SDavid Howells slot = shortcut->parent_slot; 1003cb98950SDavid Howells if (parent == stop) 1013cb98950SDavid Howells return 0; 1023cb98950SDavid Howells } 1033cb98950SDavid Howells 1043cb98950SDavid Howells /* Ascend to next slot in parent node */ 1053cb98950SDavid Howells cursor = parent; 1063cb98950SDavid Howells slot++; 1073cb98950SDavid Howells goto continue_node; 1083cb98950SDavid Howells } 1093cb98950SDavid Howells 1103cb98950SDavid Howells /** 1113cb98950SDavid Howells * assoc_array_iterate - Pass all objects in the array to a callback 1123cb98950SDavid Howells * @array: The array to iterate over. 1133cb98950SDavid Howells * @iterator: The callback function. 1143cb98950SDavid Howells * @iterator_data: Private data for the callback function. 1153cb98950SDavid Howells * 1163cb98950SDavid Howells * Iterate over all the objects in an associative array. Each one will be 1173cb98950SDavid Howells * presented to the iterator function. 1183cb98950SDavid Howells * 1193cb98950SDavid Howells * If the array is being modified concurrently with the iteration then it is 1203cb98950SDavid Howells * possible that some objects in the array will be passed to the iterator 1213cb98950SDavid Howells * callback more than once - though every object should be passed at least 1223cb98950SDavid Howells * once. If this is undesirable then the caller must lock against modification 1233cb98950SDavid Howells * for the duration of this function. 1243cb98950SDavid Howells * 1253cb98950SDavid Howells * The function will return 0 if no objects were in the array or else it will 1263cb98950SDavid Howells * return the result of the last iterator function called. Iteration stops 1273cb98950SDavid Howells * immediately if any call to the iteration function results in a non-zero 1283cb98950SDavid Howells * return. 1293cb98950SDavid Howells * 1303cb98950SDavid Howells * The caller should hold the RCU read lock or better if concurrent 1313cb98950SDavid Howells * modification is possible. 1323cb98950SDavid Howells */ 1333cb98950SDavid Howells int assoc_array_iterate(const struct assoc_array *array, 1343cb98950SDavid Howells int (*iterator)(const void *object, 1353cb98950SDavid Howells void *iterator_data), 1363cb98950SDavid Howells void *iterator_data) 1373cb98950SDavid Howells { 138516df050SPaul E. McKenney struct assoc_array_ptr *root = READ_ONCE(array->root); /* Address dependency. */ 1393cb98950SDavid Howells 1403cb98950SDavid Howells if (!root) 1413cb98950SDavid Howells return 0; 1423cb98950SDavid Howells return assoc_array_subtree_iterate(root, NULL, iterator, iterator_data); 1433cb98950SDavid Howells } 1443cb98950SDavid Howells 1453cb98950SDavid Howells enum assoc_array_walk_status { 1463cb98950SDavid Howells assoc_array_walk_tree_empty, 1473cb98950SDavid Howells assoc_array_walk_found_terminal_node, 1483cb98950SDavid Howells assoc_array_walk_found_wrong_shortcut, 14930b02c4bSStephen Hemminger }; 1503cb98950SDavid Howells 1513cb98950SDavid Howells struct assoc_array_walk_result { 1523cb98950SDavid Howells struct { 1533cb98950SDavid Howells struct assoc_array_node *node; /* Node in which leaf might be found */ 1543cb98950SDavid Howells int level; 1553cb98950SDavid Howells int slot; 1563cb98950SDavid Howells } terminal_node; 1573cb98950SDavid Howells struct { 1583cb98950SDavid Howells struct assoc_array_shortcut *shortcut; 1593cb98950SDavid Howells int level; 1603cb98950SDavid Howells int sc_level; 1613cb98950SDavid Howells unsigned long sc_segments; 1623cb98950SDavid Howells unsigned long dissimilarity; 1633cb98950SDavid Howells } wrong_shortcut; 1643cb98950SDavid Howells }; 1653cb98950SDavid Howells 1663cb98950SDavid Howells /* 1673cb98950SDavid Howells * Navigate through the internal tree looking for the closest node to the key. 1683cb98950SDavid Howells */ 1693cb98950SDavid Howells static enum assoc_array_walk_status 1703cb98950SDavid Howells assoc_array_walk(const struct assoc_array *array, 1713cb98950SDavid Howells const struct assoc_array_ops *ops, 1723cb98950SDavid Howells const void *index_key, 1733cb98950SDavid Howells struct assoc_array_walk_result *result) 1743cb98950SDavid Howells { 1753cb98950SDavid Howells struct assoc_array_shortcut *shortcut; 1763cb98950SDavid Howells struct assoc_array_node *node; 1773cb98950SDavid Howells struct assoc_array_ptr *cursor, *ptr; 1783cb98950SDavid Howells unsigned long sc_segments, dissimilarity; 1793cb98950SDavid Howells unsigned long segments; 1803cb98950SDavid Howells int level, sc_level, next_sc_level; 1813cb98950SDavid Howells int slot; 1823cb98950SDavid Howells 1833cb98950SDavid Howells pr_devel("-->%s()\n", __func__); 1843cb98950SDavid Howells 185516df050SPaul E. McKenney cursor = READ_ONCE(array->root); /* Address dependency. */ 1863cb98950SDavid Howells if (!cursor) 1873cb98950SDavid Howells return assoc_array_walk_tree_empty; 1883cb98950SDavid Howells 1893cb98950SDavid Howells level = 0; 1903cb98950SDavid Howells 1913cb98950SDavid Howells /* Use segments from the key for the new leaf to navigate through the 1923cb98950SDavid Howells * internal tree, skipping through nodes and shortcuts that are on 1933cb98950SDavid Howells * route to the destination. Eventually we'll come to a slot that is 1943cb98950SDavid Howells * either empty or contains a leaf at which point we've found a node in 1953cb98950SDavid Howells * which the leaf we're looking for might be found or into which it 1963cb98950SDavid Howells * should be inserted. 1973cb98950SDavid Howells */ 1983cb98950SDavid Howells jumped: 1993cb98950SDavid Howells segments = ops->get_key_chunk(index_key, level); 2003cb98950SDavid Howells pr_devel("segments[%d]: %lx\n", level, segments); 2013cb98950SDavid Howells 2023cb98950SDavid Howells if (assoc_array_ptr_is_shortcut(cursor)) 2033cb98950SDavid Howells goto follow_shortcut; 2043cb98950SDavid Howells 2053cb98950SDavid Howells consider_node: 2063cb98950SDavid Howells node = assoc_array_ptr_to_node(cursor); 2073cb98950SDavid Howells slot = segments >> (level & ASSOC_ARRAY_KEY_CHUNK_MASK); 2083cb98950SDavid Howells slot &= ASSOC_ARRAY_FAN_MASK; 209516df050SPaul E. McKenney ptr = READ_ONCE(node->slots[slot]); /* Address dependency. */ 2103cb98950SDavid Howells 2113cb98950SDavid Howells pr_devel("consider slot %x [ix=%d type=%lu]\n", 2123cb98950SDavid Howells slot, level, (unsigned long)ptr & 3); 2133cb98950SDavid Howells 2143cb98950SDavid Howells if (!assoc_array_ptr_is_meta(ptr)) { 2153cb98950SDavid Howells /* The node doesn't have a node/shortcut pointer in the slot 2163cb98950SDavid Howells * corresponding to the index key that we have to follow. 2173cb98950SDavid Howells */ 2183cb98950SDavid Howells result->terminal_node.node = node; 2193cb98950SDavid Howells result->terminal_node.level = level; 2203cb98950SDavid Howells result->terminal_node.slot = slot; 2213cb98950SDavid Howells pr_devel("<--%s() = terminal_node\n", __func__); 2223cb98950SDavid Howells return assoc_array_walk_found_terminal_node; 2233cb98950SDavid Howells } 2243cb98950SDavid Howells 2253cb98950SDavid Howells if (assoc_array_ptr_is_node(ptr)) { 2263cb98950SDavid Howells /* There is a pointer to a node in the slot corresponding to 2273cb98950SDavid Howells * this index key segment, so we need to follow it. 2283cb98950SDavid Howells */ 2293cb98950SDavid Howells cursor = ptr; 2303cb98950SDavid Howells level += ASSOC_ARRAY_LEVEL_STEP; 2313cb98950SDavid Howells if ((level & ASSOC_ARRAY_KEY_CHUNK_MASK) != 0) 2323cb98950SDavid Howells goto consider_node; 2333cb98950SDavid Howells goto jumped; 2343cb98950SDavid Howells } 2353cb98950SDavid Howells 2363cb98950SDavid Howells /* There is a shortcut in the slot corresponding to the index key 2373cb98950SDavid Howells * segment. We follow the shortcut if its partial index key matches 2383cb98950SDavid Howells * this leaf's. Otherwise we need to split the shortcut. 2393cb98950SDavid Howells */ 2403cb98950SDavid Howells cursor = ptr; 2413cb98950SDavid Howells follow_shortcut: 2423cb98950SDavid Howells shortcut = assoc_array_ptr_to_shortcut(cursor); 2433cb98950SDavid Howells pr_devel("shortcut to %d\n", shortcut->skip_to_level); 2443cb98950SDavid Howells sc_level = level + ASSOC_ARRAY_LEVEL_STEP; 2453cb98950SDavid Howells BUG_ON(sc_level > shortcut->skip_to_level); 2463cb98950SDavid Howells 2473cb98950SDavid Howells do { 2483cb98950SDavid Howells /* Check the leaf against the shortcut's index key a word at a 2493cb98950SDavid Howells * time, trimming the final word (the shortcut stores the index 2503cb98950SDavid Howells * key completely from the root to the shortcut's target). 2513cb98950SDavid Howells */ 2523cb98950SDavid Howells if ((sc_level & ASSOC_ARRAY_KEY_CHUNK_MASK) == 0) 2533cb98950SDavid Howells segments = ops->get_key_chunk(index_key, sc_level); 2543cb98950SDavid Howells 2553cb98950SDavid Howells sc_segments = shortcut->index_key[sc_level >> ASSOC_ARRAY_KEY_CHUNK_SHIFT]; 2563cb98950SDavid Howells dissimilarity = segments ^ sc_segments; 2573cb98950SDavid Howells 2583cb98950SDavid Howells if (round_up(sc_level, ASSOC_ARRAY_KEY_CHUNK_SIZE) > shortcut->skip_to_level) { 2593cb98950SDavid Howells /* Trim segments that are beyond the shortcut */ 2603cb98950SDavid Howells int shift = shortcut->skip_to_level & ASSOC_ARRAY_KEY_CHUNK_MASK; 2613cb98950SDavid Howells dissimilarity &= ~(ULONG_MAX << shift); 2623cb98950SDavid Howells next_sc_level = shortcut->skip_to_level; 2633cb98950SDavid Howells } else { 2643cb98950SDavid Howells next_sc_level = sc_level + ASSOC_ARRAY_KEY_CHUNK_SIZE; 2653cb98950SDavid Howells next_sc_level = round_down(next_sc_level, ASSOC_ARRAY_KEY_CHUNK_SIZE); 2663cb98950SDavid Howells } 2673cb98950SDavid Howells 2683cb98950SDavid Howells if (dissimilarity != 0) { 2693cb98950SDavid Howells /* This shortcut points elsewhere */ 2703cb98950SDavid Howells result->wrong_shortcut.shortcut = shortcut; 2713cb98950SDavid Howells result->wrong_shortcut.level = level; 2723cb98950SDavid Howells result->wrong_shortcut.sc_level = sc_level; 2733cb98950SDavid Howells result->wrong_shortcut.sc_segments = sc_segments; 2743cb98950SDavid Howells result->wrong_shortcut.dissimilarity = dissimilarity; 2753cb98950SDavid Howells return assoc_array_walk_found_wrong_shortcut; 2763cb98950SDavid Howells } 2773cb98950SDavid Howells 2783cb98950SDavid Howells sc_level = next_sc_level; 2793cb98950SDavid Howells } while (sc_level < shortcut->skip_to_level); 2803cb98950SDavid Howells 2813cb98950SDavid Howells /* The shortcut matches the leaf's index to this point. */ 282516df050SPaul E. McKenney cursor = READ_ONCE(shortcut->next_node); /* Address dependency. */ 2833cb98950SDavid Howells if (((level ^ sc_level) & ~ASSOC_ARRAY_KEY_CHUNK_MASK) != 0) { 2843cb98950SDavid Howells level = sc_level; 2853cb98950SDavid Howells goto jumped; 2863cb98950SDavid Howells } else { 2873cb98950SDavid Howells level = sc_level; 2883cb98950SDavid Howells goto consider_node; 2893cb98950SDavid Howells } 2903cb98950SDavid Howells } 2913cb98950SDavid Howells 2923cb98950SDavid Howells /** 2933cb98950SDavid Howells * assoc_array_find - Find an object by index key 2943cb98950SDavid Howells * @array: The associative array to search. 2953cb98950SDavid Howells * @ops: The operations to use. 2963cb98950SDavid Howells * @index_key: The key to the object. 2973cb98950SDavid Howells * 2983cb98950SDavid Howells * Find an object in an associative array by walking through the internal tree 2993cb98950SDavid Howells * to the node that should contain the object and then searching the leaves 3003cb98950SDavid Howells * there. NULL is returned if the requested object was not found in the array. 3013cb98950SDavid Howells * 3023cb98950SDavid Howells * The caller must hold the RCU read lock or better. 3033cb98950SDavid Howells */ 3043cb98950SDavid Howells void *assoc_array_find(const struct assoc_array *array, 3053cb98950SDavid Howells const struct assoc_array_ops *ops, 3063cb98950SDavid Howells const void *index_key) 3073cb98950SDavid Howells { 3083cb98950SDavid Howells struct assoc_array_walk_result result; 3093cb98950SDavid Howells const struct assoc_array_node *node; 3103cb98950SDavid Howells const struct assoc_array_ptr *ptr; 3113cb98950SDavid Howells const void *leaf; 3123cb98950SDavid Howells int slot; 3133cb98950SDavid Howells 3143cb98950SDavid Howells if (assoc_array_walk(array, ops, index_key, &result) != 3153cb98950SDavid Howells assoc_array_walk_found_terminal_node) 3163cb98950SDavid Howells return NULL; 3173cb98950SDavid Howells 3183cb98950SDavid Howells node = result.terminal_node.node; 3193cb98950SDavid Howells 3203cb98950SDavid Howells /* If the target key is available to us, it's has to be pointed to by 3213cb98950SDavid Howells * the terminal node. 3223cb98950SDavid Howells */ 3233cb98950SDavid Howells for (slot = 0; slot < ASSOC_ARRAY_FAN_OUT; slot++) { 324516df050SPaul E. McKenney ptr = READ_ONCE(node->slots[slot]); /* Address dependency. */ 3253cb98950SDavid Howells if (ptr && assoc_array_ptr_is_leaf(ptr)) { 3263cb98950SDavid Howells /* We need a barrier between the read of the pointer 3273cb98950SDavid Howells * and dereferencing the pointer - but only if we are 3283cb98950SDavid Howells * actually going to dereference it. 3293cb98950SDavid Howells */ 3303cb98950SDavid Howells leaf = assoc_array_ptr_to_leaf(ptr); 3313cb98950SDavid Howells if (ops->compare_object(leaf, index_key)) 3323cb98950SDavid Howells return (void *)leaf; 3333cb98950SDavid Howells } 3343cb98950SDavid Howells } 3353cb98950SDavid Howells 3363cb98950SDavid Howells return NULL; 3373cb98950SDavid Howells } 3383cb98950SDavid Howells 3393cb98950SDavid Howells /* 3403cb98950SDavid Howells * Destructively iterate over an associative array. The caller must prevent 3413cb98950SDavid Howells * other simultaneous accesses. 3423cb98950SDavid Howells */ 3433cb98950SDavid Howells static void assoc_array_destroy_subtree(struct assoc_array_ptr *root, 3443cb98950SDavid Howells const struct assoc_array_ops *ops) 3453cb98950SDavid Howells { 3463cb98950SDavid Howells struct assoc_array_shortcut *shortcut; 3473cb98950SDavid Howells struct assoc_array_node *node; 3483cb98950SDavid Howells struct assoc_array_ptr *cursor, *parent = NULL; 3493cb98950SDavid Howells int slot = -1; 3503cb98950SDavid Howells 3513cb98950SDavid Howells pr_devel("-->%s()\n", __func__); 3523cb98950SDavid Howells 3533cb98950SDavid Howells cursor = root; 3543cb98950SDavid Howells if (!cursor) { 3553cb98950SDavid Howells pr_devel("empty\n"); 3563cb98950SDavid Howells return; 3573cb98950SDavid Howells } 3583cb98950SDavid Howells 3593cb98950SDavid Howells move_to_meta: 3603cb98950SDavid Howells if (assoc_array_ptr_is_shortcut(cursor)) { 3613cb98950SDavid Howells /* Descend through a shortcut */ 3623cb98950SDavid Howells pr_devel("[%d] shortcut\n", slot); 3633cb98950SDavid Howells BUG_ON(!assoc_array_ptr_is_shortcut(cursor)); 3643cb98950SDavid Howells shortcut = assoc_array_ptr_to_shortcut(cursor); 3653cb98950SDavid Howells BUG_ON(shortcut->back_pointer != parent); 3663cb98950SDavid Howells BUG_ON(slot != -1 && shortcut->parent_slot != slot); 3673cb98950SDavid Howells parent = cursor; 3683cb98950SDavid Howells cursor = shortcut->next_node; 3693cb98950SDavid Howells slot = -1; 3703cb98950SDavid Howells BUG_ON(!assoc_array_ptr_is_node(cursor)); 3713cb98950SDavid Howells } 3723cb98950SDavid Howells 3733cb98950SDavid Howells pr_devel("[%d] node\n", slot); 3743cb98950SDavid Howells node = assoc_array_ptr_to_node(cursor); 3753cb98950SDavid Howells BUG_ON(node->back_pointer != parent); 3763cb98950SDavid Howells BUG_ON(slot != -1 && node->parent_slot != slot); 3773cb98950SDavid Howells slot = 0; 3783cb98950SDavid Howells 3793cb98950SDavid Howells continue_node: 3803cb98950SDavid Howells pr_devel("Node %p [back=%p]\n", node, node->back_pointer); 3813cb98950SDavid Howells for (; slot < ASSOC_ARRAY_FAN_OUT; slot++) { 3823cb98950SDavid Howells struct assoc_array_ptr *ptr = node->slots[slot]; 3833cb98950SDavid Howells if (!ptr) 3843cb98950SDavid Howells continue; 3853cb98950SDavid Howells if (assoc_array_ptr_is_meta(ptr)) { 3863cb98950SDavid Howells parent = cursor; 3873cb98950SDavid Howells cursor = ptr; 3883cb98950SDavid Howells goto move_to_meta; 3893cb98950SDavid Howells } 3903cb98950SDavid Howells 3913cb98950SDavid Howells if (ops) { 3923cb98950SDavid Howells pr_devel("[%d] free leaf\n", slot); 3933cb98950SDavid Howells ops->free_object(assoc_array_ptr_to_leaf(ptr)); 3943cb98950SDavid Howells } 3953cb98950SDavid Howells } 3963cb98950SDavid Howells 3973cb98950SDavid Howells parent = node->back_pointer; 3983cb98950SDavid Howells slot = node->parent_slot; 3993cb98950SDavid Howells pr_devel("free node\n"); 4003cb98950SDavid Howells kfree(node); 4013cb98950SDavid Howells if (!parent) 4023cb98950SDavid Howells return; /* Done */ 4033cb98950SDavid Howells 4043cb98950SDavid Howells /* Move back up to the parent (may need to free a shortcut on 4053cb98950SDavid Howells * the way up) */ 4063cb98950SDavid Howells if (assoc_array_ptr_is_shortcut(parent)) { 4073cb98950SDavid Howells shortcut = assoc_array_ptr_to_shortcut(parent); 4083cb98950SDavid Howells BUG_ON(shortcut->next_node != cursor); 4093cb98950SDavid Howells cursor = parent; 4103cb98950SDavid Howells parent = shortcut->back_pointer; 4113cb98950SDavid Howells slot = shortcut->parent_slot; 4123cb98950SDavid Howells pr_devel("free shortcut\n"); 4133cb98950SDavid Howells kfree(shortcut); 4143cb98950SDavid Howells if (!parent) 4153cb98950SDavid Howells return; 4163cb98950SDavid Howells 4173cb98950SDavid Howells BUG_ON(!assoc_array_ptr_is_node(parent)); 4183cb98950SDavid Howells } 4193cb98950SDavid Howells 4203cb98950SDavid Howells /* Ascend to next slot in parent node */ 4213cb98950SDavid Howells pr_devel("ascend to %p[%d]\n", parent, slot); 4223cb98950SDavid Howells cursor = parent; 4233cb98950SDavid Howells node = assoc_array_ptr_to_node(cursor); 4243cb98950SDavid Howells slot++; 4253cb98950SDavid Howells goto continue_node; 4263cb98950SDavid Howells } 4273cb98950SDavid Howells 4283cb98950SDavid Howells /** 4293cb98950SDavid Howells * assoc_array_destroy - Destroy an associative array 4303cb98950SDavid Howells * @array: The array to destroy. 4313cb98950SDavid Howells * @ops: The operations to use. 4323cb98950SDavid Howells * 4333cb98950SDavid Howells * Discard all metadata and free all objects in an associative array. The 4343cb98950SDavid Howells * array will be empty and ready to use again upon completion. This function 4353cb98950SDavid Howells * cannot fail. 4363cb98950SDavid Howells * 4373cb98950SDavid Howells * The caller must prevent all other accesses whilst this takes place as no 4383cb98950SDavid Howells * attempt is made to adjust pointers gracefully to permit RCU readlock-holding 4393cb98950SDavid Howells * accesses to continue. On the other hand, no memory allocation is required. 4403cb98950SDavid Howells */ 4413cb98950SDavid Howells void assoc_array_destroy(struct assoc_array *array, 4423cb98950SDavid Howells const struct assoc_array_ops *ops) 4433cb98950SDavid Howells { 4443cb98950SDavid Howells assoc_array_destroy_subtree(array->root, ops); 4453cb98950SDavid Howells array->root = NULL; 4463cb98950SDavid Howells } 4473cb98950SDavid Howells 4483cb98950SDavid Howells /* 4493cb98950SDavid Howells * Handle insertion into an empty tree. 4503cb98950SDavid Howells */ 4513cb98950SDavid Howells static bool assoc_array_insert_in_empty_tree(struct assoc_array_edit *edit) 4523cb98950SDavid Howells { 4533cb98950SDavid Howells struct assoc_array_node *new_n0; 4543cb98950SDavid Howells 4553cb98950SDavid Howells pr_devel("-->%s()\n", __func__); 4563cb98950SDavid Howells 4573cb98950SDavid Howells new_n0 = kzalloc(sizeof(struct assoc_array_node), GFP_KERNEL); 4583cb98950SDavid Howells if (!new_n0) 4593cb98950SDavid Howells return false; 4603cb98950SDavid Howells 4613cb98950SDavid Howells edit->new_meta[0] = assoc_array_node_to_ptr(new_n0); 4623cb98950SDavid Howells edit->leaf_p = &new_n0->slots[0]; 4633cb98950SDavid Howells edit->adjust_count_on = new_n0; 4643cb98950SDavid Howells edit->set[0].ptr = &edit->array->root; 4653cb98950SDavid Howells edit->set[0].to = assoc_array_node_to_ptr(new_n0); 4663cb98950SDavid Howells 4673cb98950SDavid Howells pr_devel("<--%s() = ok [no root]\n", __func__); 4683cb98950SDavid Howells return true; 4693cb98950SDavid Howells } 4703cb98950SDavid Howells 4713cb98950SDavid Howells /* 4723cb98950SDavid Howells * Handle insertion into a terminal node. 4733cb98950SDavid Howells */ 4743cb98950SDavid Howells static bool assoc_array_insert_into_terminal_node(struct assoc_array_edit *edit, 4753cb98950SDavid Howells const struct assoc_array_ops *ops, 4763cb98950SDavid Howells const void *index_key, 4773cb98950SDavid Howells struct assoc_array_walk_result *result) 4783cb98950SDavid Howells { 4793cb98950SDavid Howells struct assoc_array_shortcut *shortcut, *new_s0; 4803cb98950SDavid Howells struct assoc_array_node *node, *new_n0, *new_n1, *side; 4813cb98950SDavid Howells struct assoc_array_ptr *ptr; 4823cb98950SDavid Howells unsigned long dissimilarity, base_seg, blank; 4833cb98950SDavid Howells size_t keylen; 4843cb98950SDavid Howells bool have_meta; 4853cb98950SDavid Howells int level, diff; 4863cb98950SDavid Howells int slot, next_slot, free_slot, i, j; 4873cb98950SDavid Howells 4883cb98950SDavid Howells node = result->terminal_node.node; 4893cb98950SDavid Howells level = result->terminal_node.level; 4903cb98950SDavid Howells edit->segment_cache[ASSOC_ARRAY_FAN_OUT] = result->terminal_node.slot; 4913cb98950SDavid Howells 4923cb98950SDavid Howells pr_devel("-->%s()\n", __func__); 4933cb98950SDavid Howells 4943cb98950SDavid Howells /* We arrived at a node which doesn't have an onward node or shortcut 4953cb98950SDavid Howells * pointer that we have to follow. This means that (a) the leaf we 4963cb98950SDavid Howells * want must go here (either by insertion or replacement) or (b) we 4973cb98950SDavid Howells * need to split this node and insert in one of the fragments. 4983cb98950SDavid Howells */ 4993cb98950SDavid Howells free_slot = -1; 5003cb98950SDavid Howells 5013cb98950SDavid Howells /* Firstly, we have to check the leaves in this node to see if there's 5023cb98950SDavid Howells * a matching one we should replace in place. 5033cb98950SDavid Howells */ 5043cb98950SDavid Howells for (i = 0; i < ASSOC_ARRAY_FAN_OUT; i++) { 5053cb98950SDavid Howells ptr = node->slots[i]; 5063cb98950SDavid Howells if (!ptr) { 5073cb98950SDavid Howells free_slot = i; 5083cb98950SDavid Howells continue; 5093cb98950SDavid Howells } 5108d4a2ec1SJerome Marchand if (assoc_array_ptr_is_leaf(ptr) && 5118d4a2ec1SJerome Marchand ops->compare_object(assoc_array_ptr_to_leaf(ptr), 5128d4a2ec1SJerome Marchand index_key)) { 5133cb98950SDavid Howells pr_devel("replace in slot %d\n", i); 5143cb98950SDavid Howells edit->leaf_p = &node->slots[i]; 5153cb98950SDavid Howells edit->dead_leaf = node->slots[i]; 5163cb98950SDavid Howells pr_devel("<--%s() = ok [replace]\n", __func__); 5173cb98950SDavid Howells return true; 5183cb98950SDavid Howells } 5193cb98950SDavid Howells } 5203cb98950SDavid Howells 5213cb98950SDavid Howells /* If there is a free slot in this node then we can just insert the 5223cb98950SDavid Howells * leaf here. 5233cb98950SDavid Howells */ 5243cb98950SDavid Howells if (free_slot >= 0) { 5253cb98950SDavid Howells pr_devel("insert in free slot %d\n", free_slot); 5263cb98950SDavid Howells edit->leaf_p = &node->slots[free_slot]; 5273cb98950SDavid Howells edit->adjust_count_on = node; 5283cb98950SDavid Howells pr_devel("<--%s() = ok [insert]\n", __func__); 5293cb98950SDavid Howells return true; 5303cb98950SDavid Howells } 5313cb98950SDavid Howells 5323cb98950SDavid Howells /* The node has no spare slots - so we're either going to have to split 5333cb98950SDavid Howells * it or insert another node before it. 5343cb98950SDavid Howells * 5353cb98950SDavid Howells * Whatever, we're going to need at least two new nodes - so allocate 5363cb98950SDavid Howells * those now. We may also need a new shortcut, but we deal with that 5373cb98950SDavid Howells * when we need it. 5383cb98950SDavid Howells */ 5393cb98950SDavid Howells new_n0 = kzalloc(sizeof(struct assoc_array_node), GFP_KERNEL); 5403cb98950SDavid Howells if (!new_n0) 5413cb98950SDavid Howells return false; 5423cb98950SDavid Howells edit->new_meta[0] = assoc_array_node_to_ptr(new_n0); 5433cb98950SDavid Howells new_n1 = kzalloc(sizeof(struct assoc_array_node), GFP_KERNEL); 5443cb98950SDavid Howells if (!new_n1) 5453cb98950SDavid Howells return false; 5463cb98950SDavid Howells edit->new_meta[1] = assoc_array_node_to_ptr(new_n1); 5473cb98950SDavid Howells 5483cb98950SDavid Howells /* We need to find out how similar the leaves are. */ 5493cb98950SDavid Howells pr_devel("no spare slots\n"); 5503cb98950SDavid Howells have_meta = false; 5513cb98950SDavid Howells for (i = 0; i < ASSOC_ARRAY_FAN_OUT; i++) { 5523cb98950SDavid Howells ptr = node->slots[i]; 5533cb98950SDavid Howells if (assoc_array_ptr_is_meta(ptr)) { 5543cb98950SDavid Howells edit->segment_cache[i] = 0xff; 5553cb98950SDavid Howells have_meta = true; 5563cb98950SDavid Howells continue; 5573cb98950SDavid Howells } 5583cb98950SDavid Howells base_seg = ops->get_object_key_chunk( 5593cb98950SDavid Howells assoc_array_ptr_to_leaf(ptr), level); 5603cb98950SDavid Howells base_seg >>= level & ASSOC_ARRAY_KEY_CHUNK_MASK; 5613cb98950SDavid Howells edit->segment_cache[i] = base_seg & ASSOC_ARRAY_FAN_MASK; 5623cb98950SDavid Howells } 5633cb98950SDavid Howells 5643cb98950SDavid Howells if (have_meta) { 5653cb98950SDavid Howells pr_devel("have meta\n"); 5663cb98950SDavid Howells goto split_node; 5673cb98950SDavid Howells } 5683cb98950SDavid Howells 5693cb98950SDavid Howells /* The node contains only leaves */ 5703cb98950SDavid Howells dissimilarity = 0; 5713cb98950SDavid Howells base_seg = edit->segment_cache[0]; 5723cb98950SDavid Howells for (i = 1; i < ASSOC_ARRAY_FAN_OUT; i++) 5733cb98950SDavid Howells dissimilarity |= edit->segment_cache[i] ^ base_seg; 5743cb98950SDavid Howells 5753cb98950SDavid Howells pr_devel("only leaves; dissimilarity=%lx\n", dissimilarity); 5763cb98950SDavid Howells 5773cb98950SDavid Howells if ((dissimilarity & ASSOC_ARRAY_FAN_MASK) == 0) { 5783cb98950SDavid Howells /* The old leaves all cluster in the same slot. We will need 5793cb98950SDavid Howells * to insert a shortcut if the new node wants to cluster with them. 5803cb98950SDavid Howells */ 5813cb98950SDavid Howells if ((edit->segment_cache[ASSOC_ARRAY_FAN_OUT] ^ base_seg) == 0) 5823cb98950SDavid Howells goto all_leaves_cluster_together; 5833cb98950SDavid Howells 584ea678998SDavid Howells /* Otherwise all the old leaves cluster in the same slot, but 585ea678998SDavid Howells * the new leaf wants to go into a different slot - so we 586ea678998SDavid Howells * create a new node (n0) to hold the new leaf and a pointer to 587ea678998SDavid Howells * a new node (n1) holding all the old leaves. 588ea678998SDavid Howells * 589ea678998SDavid Howells * This can be done by falling through to the node splitting 590ea678998SDavid Howells * path. 5913cb98950SDavid Howells */ 592ea678998SDavid Howells pr_devel("present leaves cluster but not new leaf\n"); 5933cb98950SDavid Howells } 5943cb98950SDavid Howells 5953cb98950SDavid Howells split_node: 5963cb98950SDavid Howells pr_devel("split node\n"); 5973cb98950SDavid Howells 598ea678998SDavid Howells /* We need to split the current node. The node must contain anything 599ea678998SDavid Howells * from a single leaf (in the one leaf case, this leaf will cluster 600ea678998SDavid Howells * with the new leaf) and the rest meta-pointers, to all leaves, some 601ea678998SDavid Howells * of which may cluster. 602ea678998SDavid Howells * 603ea678998SDavid Howells * It won't contain the case in which all the current leaves plus the 604ea678998SDavid Howells * new leaves want to cluster in the same slot. 6053cb98950SDavid Howells * 6063cb98950SDavid Howells * We need to expel at least two leaves out of a set consisting of the 607ea678998SDavid Howells * leaves in the node and the new leaf. The current meta pointers can 608ea678998SDavid Howells * just be copied as they shouldn't cluster with any of the leaves. 6093cb98950SDavid Howells * 6103cb98950SDavid Howells * We need a new node (n0) to replace the current one and a new node to 6113cb98950SDavid Howells * take the expelled nodes (n1). 6123cb98950SDavid Howells */ 6133cb98950SDavid Howells edit->set[0].to = assoc_array_node_to_ptr(new_n0); 6143cb98950SDavid Howells new_n0->back_pointer = node->back_pointer; 6153cb98950SDavid Howells new_n0->parent_slot = node->parent_slot; 6163cb98950SDavid Howells new_n1->back_pointer = assoc_array_node_to_ptr(new_n0); 6173cb98950SDavid Howells new_n1->parent_slot = -1; /* Need to calculate this */ 6183cb98950SDavid Howells 6193cb98950SDavid Howells do_split_node: 6203cb98950SDavid Howells pr_devel("do_split_node\n"); 6213cb98950SDavid Howells 6223cb98950SDavid Howells new_n0->nr_leaves_on_branch = node->nr_leaves_on_branch; 6233cb98950SDavid Howells new_n1->nr_leaves_on_branch = 0; 6243cb98950SDavid Howells 6253cb98950SDavid Howells /* Begin by finding two matching leaves. There have to be at least two 6263cb98950SDavid Howells * that match - even if there are meta pointers - because any leaf that 6273cb98950SDavid Howells * would match a slot with a meta pointer in it must be somewhere 6283cb98950SDavid Howells * behind that meta pointer and cannot be here. Further, given N 6293cb98950SDavid Howells * remaining leaf slots, we now have N+1 leaves to go in them. 6303cb98950SDavid Howells */ 6313cb98950SDavid Howells for (i = 0; i < ASSOC_ARRAY_FAN_OUT; i++) { 6323cb98950SDavid Howells slot = edit->segment_cache[i]; 6333cb98950SDavid Howells if (slot != 0xff) 6343cb98950SDavid Howells for (j = i + 1; j < ASSOC_ARRAY_FAN_OUT + 1; j++) 6353cb98950SDavid Howells if (edit->segment_cache[j] == slot) 6363cb98950SDavid Howells goto found_slot_for_multiple_occupancy; 6373cb98950SDavid Howells } 6383cb98950SDavid Howells found_slot_for_multiple_occupancy: 6393cb98950SDavid Howells pr_devel("same slot: %x %x [%02x]\n", i, j, slot); 6403cb98950SDavid Howells BUG_ON(i >= ASSOC_ARRAY_FAN_OUT); 6413cb98950SDavid Howells BUG_ON(j >= ASSOC_ARRAY_FAN_OUT + 1); 6423cb98950SDavid Howells BUG_ON(slot >= ASSOC_ARRAY_FAN_OUT); 6433cb98950SDavid Howells 6443cb98950SDavid Howells new_n1->parent_slot = slot; 6453cb98950SDavid Howells 6463cb98950SDavid Howells /* Metadata pointers cannot change slot */ 6473cb98950SDavid Howells for (i = 0; i < ASSOC_ARRAY_FAN_OUT; i++) 6483cb98950SDavid Howells if (assoc_array_ptr_is_meta(node->slots[i])) 6493cb98950SDavid Howells new_n0->slots[i] = node->slots[i]; 6503cb98950SDavid Howells else 6513cb98950SDavid Howells new_n0->slots[i] = NULL; 6523cb98950SDavid Howells BUG_ON(new_n0->slots[slot] != NULL); 6533cb98950SDavid Howells new_n0->slots[slot] = assoc_array_node_to_ptr(new_n1); 6543cb98950SDavid Howells 6553cb98950SDavid Howells /* Filter the leaf pointers between the new nodes */ 6563cb98950SDavid Howells free_slot = -1; 6573cb98950SDavid Howells next_slot = 0; 6583cb98950SDavid Howells for (i = 0; i < ASSOC_ARRAY_FAN_OUT; i++) { 6593cb98950SDavid Howells if (assoc_array_ptr_is_meta(node->slots[i])) 6603cb98950SDavid Howells continue; 6613cb98950SDavid Howells if (edit->segment_cache[i] == slot) { 6623cb98950SDavid Howells new_n1->slots[next_slot++] = node->slots[i]; 6633cb98950SDavid Howells new_n1->nr_leaves_on_branch++; 6643cb98950SDavid Howells } else { 6653cb98950SDavid Howells do { 6663cb98950SDavid Howells free_slot++; 6673cb98950SDavid Howells } while (new_n0->slots[free_slot] != NULL); 6683cb98950SDavid Howells new_n0->slots[free_slot] = node->slots[i]; 6693cb98950SDavid Howells } 6703cb98950SDavid Howells } 6713cb98950SDavid Howells 6723cb98950SDavid Howells pr_devel("filtered: f=%x n=%x\n", free_slot, next_slot); 6733cb98950SDavid Howells 6743cb98950SDavid Howells if (edit->segment_cache[ASSOC_ARRAY_FAN_OUT] != slot) { 6753cb98950SDavid Howells do { 6763cb98950SDavid Howells free_slot++; 6773cb98950SDavid Howells } while (new_n0->slots[free_slot] != NULL); 6783cb98950SDavid Howells edit->leaf_p = &new_n0->slots[free_slot]; 6793cb98950SDavid Howells edit->adjust_count_on = new_n0; 6803cb98950SDavid Howells } else { 6813cb98950SDavid Howells edit->leaf_p = &new_n1->slots[next_slot++]; 6823cb98950SDavid Howells edit->adjust_count_on = new_n1; 6833cb98950SDavid Howells } 6843cb98950SDavid Howells 6853cb98950SDavid Howells BUG_ON(next_slot <= 1); 6863cb98950SDavid Howells 6873cb98950SDavid Howells edit->set_backpointers_to = assoc_array_node_to_ptr(new_n0); 6883cb98950SDavid Howells for (i = 0; i < ASSOC_ARRAY_FAN_OUT; i++) { 6893cb98950SDavid Howells if (edit->segment_cache[i] == 0xff) { 6903cb98950SDavid Howells ptr = node->slots[i]; 6913cb98950SDavid Howells BUG_ON(assoc_array_ptr_is_leaf(ptr)); 6923cb98950SDavid Howells if (assoc_array_ptr_is_node(ptr)) { 6933cb98950SDavid Howells side = assoc_array_ptr_to_node(ptr); 6943cb98950SDavid Howells edit->set_backpointers[i] = &side->back_pointer; 6953cb98950SDavid Howells } else { 6963cb98950SDavid Howells shortcut = assoc_array_ptr_to_shortcut(ptr); 6973cb98950SDavid Howells edit->set_backpointers[i] = &shortcut->back_pointer; 6983cb98950SDavid Howells } 6993cb98950SDavid Howells } 7003cb98950SDavid Howells } 7013cb98950SDavid Howells 7023cb98950SDavid Howells ptr = node->back_pointer; 7033cb98950SDavid Howells if (!ptr) 7043cb98950SDavid Howells edit->set[0].ptr = &edit->array->root; 7053cb98950SDavid Howells else if (assoc_array_ptr_is_node(ptr)) 7063cb98950SDavid Howells edit->set[0].ptr = &assoc_array_ptr_to_node(ptr)->slots[node->parent_slot]; 7073cb98950SDavid Howells else 7083cb98950SDavid Howells edit->set[0].ptr = &assoc_array_ptr_to_shortcut(ptr)->next_node; 7093cb98950SDavid Howells edit->excised_meta[0] = assoc_array_node_to_ptr(node); 7103cb98950SDavid Howells pr_devel("<--%s() = ok [split node]\n", __func__); 7113cb98950SDavid Howells return true; 7123cb98950SDavid Howells 7133cb98950SDavid Howells all_leaves_cluster_together: 7143cb98950SDavid Howells /* All the leaves, new and old, want to cluster together in this node 7153cb98950SDavid Howells * in the same slot, so we have to replace this node with a shortcut to 7163cb98950SDavid Howells * skip over the identical parts of the key and then place a pair of 7173cb98950SDavid Howells * nodes, one inside the other, at the end of the shortcut and 7183cb98950SDavid Howells * distribute the keys between them. 7193cb98950SDavid Howells * 7203cb98950SDavid Howells * Firstly we need to work out where the leaves start diverging as a 7213cb98950SDavid Howells * bit position into their keys so that we know how big the shortcut 7223cb98950SDavid Howells * needs to be. 7233cb98950SDavid Howells * 7243cb98950SDavid Howells * We only need to make a single pass of N of the N+1 leaves because if 7253cb98950SDavid Howells * any keys differ between themselves at bit X then at least one of 7263cb98950SDavid Howells * them must also differ with the base key at bit X or before. 7273cb98950SDavid Howells */ 7283cb98950SDavid Howells pr_devel("all leaves cluster together\n"); 7293cb98950SDavid Howells diff = INT_MAX; 7303cb98950SDavid Howells for (i = 0; i < ASSOC_ARRAY_FAN_OUT; i++) { 73123fd78d7SDavid Howells int x = ops->diff_objects(assoc_array_ptr_to_leaf(node->slots[i]), 73223fd78d7SDavid Howells index_key); 7333cb98950SDavid Howells if (x < diff) { 7343cb98950SDavid Howells BUG_ON(x < 0); 7353cb98950SDavid Howells diff = x; 7363cb98950SDavid Howells } 7373cb98950SDavid Howells } 7383cb98950SDavid Howells BUG_ON(diff == INT_MAX); 7393cb98950SDavid Howells BUG_ON(diff < level + ASSOC_ARRAY_LEVEL_STEP); 7403cb98950SDavid Howells 7413cb98950SDavid Howells keylen = round_up(diff, ASSOC_ARRAY_KEY_CHUNK_SIZE); 7423cb98950SDavid Howells keylen >>= ASSOC_ARRAY_KEY_CHUNK_SHIFT; 7433cb98950SDavid Howells 744*2a12e000SLen Baker new_s0 = kzalloc(struct_size(new_s0, index_key, keylen), GFP_KERNEL); 7453cb98950SDavid Howells if (!new_s0) 7463cb98950SDavid Howells return false; 7473cb98950SDavid Howells edit->new_meta[2] = assoc_array_shortcut_to_ptr(new_s0); 7483cb98950SDavid Howells 7493cb98950SDavid Howells edit->set[0].to = assoc_array_shortcut_to_ptr(new_s0); 7503cb98950SDavid Howells new_s0->back_pointer = node->back_pointer; 7513cb98950SDavid Howells new_s0->parent_slot = node->parent_slot; 7523cb98950SDavid Howells new_s0->next_node = assoc_array_node_to_ptr(new_n0); 7533cb98950SDavid Howells new_n0->back_pointer = assoc_array_shortcut_to_ptr(new_s0); 7543cb98950SDavid Howells new_n0->parent_slot = 0; 7553cb98950SDavid Howells new_n1->back_pointer = assoc_array_node_to_ptr(new_n0); 7563cb98950SDavid Howells new_n1->parent_slot = -1; /* Need to calculate this */ 7573cb98950SDavid Howells 7583cb98950SDavid Howells new_s0->skip_to_level = level = diff & ~ASSOC_ARRAY_LEVEL_STEP_MASK; 7593cb98950SDavid Howells pr_devel("skip_to_level = %d [diff %d]\n", level, diff); 7603cb98950SDavid Howells BUG_ON(level <= 0); 7613cb98950SDavid Howells 7623cb98950SDavid Howells for (i = 0; i < keylen; i++) 7633cb98950SDavid Howells new_s0->index_key[i] = 7643cb98950SDavid Howells ops->get_key_chunk(index_key, i * ASSOC_ARRAY_KEY_CHUNK_SIZE); 7653cb98950SDavid Howells 766bb2ba2d7SDavid Howells if (level & ASSOC_ARRAY_KEY_CHUNK_MASK) { 7673cb98950SDavid Howells blank = ULONG_MAX << (level & ASSOC_ARRAY_KEY_CHUNK_MASK); 7683cb98950SDavid Howells pr_devel("blank off [%zu] %d: %lx\n", keylen - 1, level, blank); 7693cb98950SDavid Howells new_s0->index_key[keylen - 1] &= ~blank; 770bb2ba2d7SDavid Howells } 7713cb98950SDavid Howells 7723cb98950SDavid Howells /* This now reduces to a node splitting exercise for which we'll need 7733cb98950SDavid Howells * to regenerate the disparity table. 7743cb98950SDavid Howells */ 7753cb98950SDavid Howells for (i = 0; i < ASSOC_ARRAY_FAN_OUT; i++) { 7763cb98950SDavid Howells ptr = node->slots[i]; 7773cb98950SDavid Howells base_seg = ops->get_object_key_chunk(assoc_array_ptr_to_leaf(ptr), 7783cb98950SDavid Howells level); 7793cb98950SDavid Howells base_seg >>= level & ASSOC_ARRAY_KEY_CHUNK_MASK; 7803cb98950SDavid Howells edit->segment_cache[i] = base_seg & ASSOC_ARRAY_FAN_MASK; 7813cb98950SDavid Howells } 7823cb98950SDavid Howells 7833cb98950SDavid Howells base_seg = ops->get_key_chunk(index_key, level); 7843cb98950SDavid Howells base_seg >>= level & ASSOC_ARRAY_KEY_CHUNK_MASK; 7853cb98950SDavid Howells edit->segment_cache[ASSOC_ARRAY_FAN_OUT] = base_seg & ASSOC_ARRAY_FAN_MASK; 7863cb98950SDavid Howells goto do_split_node; 7873cb98950SDavid Howells } 7883cb98950SDavid Howells 7893cb98950SDavid Howells /* 7903cb98950SDavid Howells * Handle insertion into the middle of a shortcut. 7913cb98950SDavid Howells */ 7923cb98950SDavid Howells static bool assoc_array_insert_mid_shortcut(struct assoc_array_edit *edit, 7933cb98950SDavid Howells const struct assoc_array_ops *ops, 7943cb98950SDavid Howells struct assoc_array_walk_result *result) 7953cb98950SDavid Howells { 7963cb98950SDavid Howells struct assoc_array_shortcut *shortcut, *new_s0, *new_s1; 7973cb98950SDavid Howells struct assoc_array_node *node, *new_n0, *side; 7983cb98950SDavid Howells unsigned long sc_segments, dissimilarity, blank; 7993cb98950SDavid Howells size_t keylen; 8003cb98950SDavid Howells int level, sc_level, diff; 8013cb98950SDavid Howells int sc_slot; 8023cb98950SDavid Howells 8033cb98950SDavid Howells shortcut = result->wrong_shortcut.shortcut; 8043cb98950SDavid Howells level = result->wrong_shortcut.level; 8053cb98950SDavid Howells sc_level = result->wrong_shortcut.sc_level; 8063cb98950SDavid Howells sc_segments = result->wrong_shortcut.sc_segments; 8073cb98950SDavid Howells dissimilarity = result->wrong_shortcut.dissimilarity; 8083cb98950SDavid Howells 8093cb98950SDavid Howells pr_devel("-->%s(ix=%d dis=%lx scix=%d)\n", 8103cb98950SDavid Howells __func__, level, dissimilarity, sc_level); 8113cb98950SDavid Howells 8123cb98950SDavid Howells /* We need to split a shortcut and insert a node between the two 8133cb98950SDavid Howells * pieces. Zero-length pieces will be dispensed with entirely. 8143cb98950SDavid Howells * 8153cb98950SDavid Howells * First of all, we need to find out in which level the first 8163cb98950SDavid Howells * difference was. 8173cb98950SDavid Howells */ 8183cb98950SDavid Howells diff = __ffs(dissimilarity); 8193cb98950SDavid Howells diff &= ~ASSOC_ARRAY_LEVEL_STEP_MASK; 8203cb98950SDavid Howells diff += sc_level & ~ASSOC_ARRAY_KEY_CHUNK_MASK; 8213cb98950SDavid Howells pr_devel("diff=%d\n", diff); 8223cb98950SDavid Howells 8233cb98950SDavid Howells if (!shortcut->back_pointer) { 8243cb98950SDavid Howells edit->set[0].ptr = &edit->array->root; 8253cb98950SDavid Howells } else if (assoc_array_ptr_is_node(shortcut->back_pointer)) { 8263cb98950SDavid Howells node = assoc_array_ptr_to_node(shortcut->back_pointer); 8273cb98950SDavid Howells edit->set[0].ptr = &node->slots[shortcut->parent_slot]; 8283cb98950SDavid Howells } else { 8293cb98950SDavid Howells BUG(); 8303cb98950SDavid Howells } 8313cb98950SDavid Howells 8323cb98950SDavid Howells edit->excised_meta[0] = assoc_array_shortcut_to_ptr(shortcut); 8333cb98950SDavid Howells 8343cb98950SDavid Howells /* Create a new node now since we're going to need it anyway */ 8353cb98950SDavid Howells new_n0 = kzalloc(sizeof(struct assoc_array_node), GFP_KERNEL); 8363cb98950SDavid Howells if (!new_n0) 8373cb98950SDavid Howells return false; 8383cb98950SDavid Howells edit->new_meta[0] = assoc_array_node_to_ptr(new_n0); 8393cb98950SDavid Howells edit->adjust_count_on = new_n0; 8403cb98950SDavid Howells 8413cb98950SDavid Howells /* Insert a new shortcut before the new node if this segment isn't of 8423cb98950SDavid Howells * zero length - otherwise we just connect the new node directly to the 8433cb98950SDavid Howells * parent. 8443cb98950SDavid Howells */ 8453cb98950SDavid Howells level += ASSOC_ARRAY_LEVEL_STEP; 8463cb98950SDavid Howells if (diff > level) { 8473cb98950SDavid Howells pr_devel("pre-shortcut %d...%d\n", level, diff); 8483cb98950SDavid Howells keylen = round_up(diff, ASSOC_ARRAY_KEY_CHUNK_SIZE); 8493cb98950SDavid Howells keylen >>= ASSOC_ARRAY_KEY_CHUNK_SHIFT; 8503cb98950SDavid Howells 851*2a12e000SLen Baker new_s0 = kzalloc(struct_size(new_s0, index_key, keylen), 852*2a12e000SLen Baker GFP_KERNEL); 8533cb98950SDavid Howells if (!new_s0) 8543cb98950SDavid Howells return false; 8553cb98950SDavid Howells edit->new_meta[1] = assoc_array_shortcut_to_ptr(new_s0); 8563cb98950SDavid Howells edit->set[0].to = assoc_array_shortcut_to_ptr(new_s0); 8573cb98950SDavid Howells new_s0->back_pointer = shortcut->back_pointer; 8583cb98950SDavid Howells new_s0->parent_slot = shortcut->parent_slot; 8593cb98950SDavid Howells new_s0->next_node = assoc_array_node_to_ptr(new_n0); 8603cb98950SDavid Howells new_s0->skip_to_level = diff; 8613cb98950SDavid Howells 8623cb98950SDavid Howells new_n0->back_pointer = assoc_array_shortcut_to_ptr(new_s0); 8633cb98950SDavid Howells new_n0->parent_slot = 0; 8643cb98950SDavid Howells 8653cb98950SDavid Howells memcpy(new_s0->index_key, shortcut->index_key, 866*2a12e000SLen Baker flex_array_size(new_s0, index_key, keylen)); 8673cb98950SDavid Howells 8683cb98950SDavid Howells blank = ULONG_MAX << (diff & ASSOC_ARRAY_KEY_CHUNK_MASK); 8693cb98950SDavid Howells pr_devel("blank off [%zu] %d: %lx\n", keylen - 1, diff, blank); 8703cb98950SDavid Howells new_s0->index_key[keylen - 1] &= ~blank; 8713cb98950SDavid Howells } else { 8723cb98950SDavid Howells pr_devel("no pre-shortcut\n"); 8733cb98950SDavid Howells edit->set[0].to = assoc_array_node_to_ptr(new_n0); 8743cb98950SDavid Howells new_n0->back_pointer = shortcut->back_pointer; 8753cb98950SDavid Howells new_n0->parent_slot = shortcut->parent_slot; 8763cb98950SDavid Howells } 8773cb98950SDavid Howells 8783cb98950SDavid Howells side = assoc_array_ptr_to_node(shortcut->next_node); 8793cb98950SDavid Howells new_n0->nr_leaves_on_branch = side->nr_leaves_on_branch; 8803cb98950SDavid Howells 8813cb98950SDavid Howells /* We need to know which slot in the new node is going to take a 8823cb98950SDavid Howells * metadata pointer. 8833cb98950SDavid Howells */ 8843cb98950SDavid Howells sc_slot = sc_segments >> (diff & ASSOC_ARRAY_KEY_CHUNK_MASK); 8853cb98950SDavid Howells sc_slot &= ASSOC_ARRAY_FAN_MASK; 8863cb98950SDavid Howells 8873cb98950SDavid Howells pr_devel("new slot %lx >> %d -> %d\n", 8883cb98950SDavid Howells sc_segments, diff & ASSOC_ARRAY_KEY_CHUNK_MASK, sc_slot); 8893cb98950SDavid Howells 8903cb98950SDavid Howells /* Determine whether we need to follow the new node with a replacement 8913cb98950SDavid Howells * for the current shortcut. We could in theory reuse the current 8923cb98950SDavid Howells * shortcut if its parent slot number doesn't change - but that's a 8933cb98950SDavid Howells * 1-in-16 chance so not worth expending the code upon. 8943cb98950SDavid Howells */ 8953cb98950SDavid Howells level = diff + ASSOC_ARRAY_LEVEL_STEP; 8963cb98950SDavid Howells if (level < shortcut->skip_to_level) { 8973cb98950SDavid Howells pr_devel("post-shortcut %d...%d\n", level, shortcut->skip_to_level); 8983cb98950SDavid Howells keylen = round_up(shortcut->skip_to_level, ASSOC_ARRAY_KEY_CHUNK_SIZE); 8993cb98950SDavid Howells keylen >>= ASSOC_ARRAY_KEY_CHUNK_SHIFT; 9003cb98950SDavid Howells 901*2a12e000SLen Baker new_s1 = kzalloc(struct_size(new_s1, index_key, keylen), 902*2a12e000SLen Baker GFP_KERNEL); 9033cb98950SDavid Howells if (!new_s1) 9043cb98950SDavid Howells return false; 9053cb98950SDavid Howells edit->new_meta[2] = assoc_array_shortcut_to_ptr(new_s1); 9063cb98950SDavid Howells 9073cb98950SDavid Howells new_s1->back_pointer = assoc_array_node_to_ptr(new_n0); 9083cb98950SDavid Howells new_s1->parent_slot = sc_slot; 9093cb98950SDavid Howells new_s1->next_node = shortcut->next_node; 9103cb98950SDavid Howells new_s1->skip_to_level = shortcut->skip_to_level; 9113cb98950SDavid Howells 9123cb98950SDavid Howells new_n0->slots[sc_slot] = assoc_array_shortcut_to_ptr(new_s1); 9133cb98950SDavid Howells 9143cb98950SDavid Howells memcpy(new_s1->index_key, shortcut->index_key, 915*2a12e000SLen Baker flex_array_size(new_s1, index_key, keylen)); 9163cb98950SDavid Howells 9173cb98950SDavid Howells edit->set[1].ptr = &side->back_pointer; 9183cb98950SDavid Howells edit->set[1].to = assoc_array_shortcut_to_ptr(new_s1); 9193cb98950SDavid Howells } else { 9203cb98950SDavid Howells pr_devel("no post-shortcut\n"); 9213cb98950SDavid Howells 9223cb98950SDavid Howells /* We don't have to replace the pointed-to node as long as we 9233cb98950SDavid Howells * use memory barriers to make sure the parent slot number is 9243cb98950SDavid Howells * changed before the back pointer (the parent slot number is 9253cb98950SDavid Howells * irrelevant to the old parent shortcut). 9263cb98950SDavid Howells */ 9273cb98950SDavid Howells new_n0->slots[sc_slot] = shortcut->next_node; 9283cb98950SDavid Howells edit->set_parent_slot[0].p = &side->parent_slot; 9293cb98950SDavid Howells edit->set_parent_slot[0].to = sc_slot; 9303cb98950SDavid Howells edit->set[1].ptr = &side->back_pointer; 9313cb98950SDavid Howells edit->set[1].to = assoc_array_node_to_ptr(new_n0); 9323cb98950SDavid Howells } 9333cb98950SDavid Howells 9343cb98950SDavid Howells /* Install the new leaf in a spare slot in the new node. */ 9353cb98950SDavid Howells if (sc_slot == 0) 9363cb98950SDavid Howells edit->leaf_p = &new_n0->slots[1]; 9373cb98950SDavid Howells else 9383cb98950SDavid Howells edit->leaf_p = &new_n0->slots[0]; 9393cb98950SDavid Howells 9403cb98950SDavid Howells pr_devel("<--%s() = ok [split shortcut]\n", __func__); 9413cb98950SDavid Howells return edit; 9423cb98950SDavid Howells } 9433cb98950SDavid Howells 9443cb98950SDavid Howells /** 9453cb98950SDavid Howells * assoc_array_insert - Script insertion of an object into an associative array 9463cb98950SDavid Howells * @array: The array to insert into. 9473cb98950SDavid Howells * @ops: The operations to use. 9483cb98950SDavid Howells * @index_key: The key to insert at. 9493cb98950SDavid Howells * @object: The object to insert. 9503cb98950SDavid Howells * 9513cb98950SDavid Howells * Precalculate and preallocate a script for the insertion or replacement of an 9523cb98950SDavid Howells * object in an associative array. This results in an edit script that can 9533cb98950SDavid Howells * either be applied or cancelled. 9543cb98950SDavid Howells * 9553cb98950SDavid Howells * The function returns a pointer to an edit script or -ENOMEM. 9563cb98950SDavid Howells * 9573cb98950SDavid Howells * The caller should lock against other modifications and must continue to hold 9583cb98950SDavid Howells * the lock until assoc_array_apply_edit() has been called. 9593cb98950SDavid Howells * 9603cb98950SDavid Howells * Accesses to the tree may take place concurrently with this function, 9613cb98950SDavid Howells * provided they hold the RCU read lock. 9623cb98950SDavid Howells */ 9633cb98950SDavid Howells struct assoc_array_edit *assoc_array_insert(struct assoc_array *array, 9643cb98950SDavid Howells const struct assoc_array_ops *ops, 9653cb98950SDavid Howells const void *index_key, 9663cb98950SDavid Howells void *object) 9673cb98950SDavid Howells { 9683cb98950SDavid Howells struct assoc_array_walk_result result; 9693cb98950SDavid Howells struct assoc_array_edit *edit; 9703cb98950SDavid Howells 9713cb98950SDavid Howells pr_devel("-->%s()\n", __func__); 9723cb98950SDavid Howells 9733cb98950SDavid Howells /* The leaf pointer we're given must not have the bottom bit set as we 9743cb98950SDavid Howells * use those for type-marking the pointer. NULL pointers are also not 9753cb98950SDavid Howells * allowed as they indicate an empty slot but we have to allow them 9763cb98950SDavid Howells * here as they can be updated later. 9773cb98950SDavid Howells */ 9783cb98950SDavid Howells BUG_ON(assoc_array_ptr_is_meta(object)); 9793cb98950SDavid Howells 9803cb98950SDavid Howells edit = kzalloc(sizeof(struct assoc_array_edit), GFP_KERNEL); 9813cb98950SDavid Howells if (!edit) 9823cb98950SDavid Howells return ERR_PTR(-ENOMEM); 9833cb98950SDavid Howells edit->array = array; 9843cb98950SDavid Howells edit->ops = ops; 9853cb98950SDavid Howells edit->leaf = assoc_array_leaf_to_ptr(object); 9863cb98950SDavid Howells edit->adjust_count_by = 1; 9873cb98950SDavid Howells 9883cb98950SDavid Howells switch (assoc_array_walk(array, ops, index_key, &result)) { 9893cb98950SDavid Howells case assoc_array_walk_tree_empty: 9903cb98950SDavid Howells /* Allocate a root node if there isn't one yet */ 9913cb98950SDavid Howells if (!assoc_array_insert_in_empty_tree(edit)) 9923cb98950SDavid Howells goto enomem; 9933cb98950SDavid Howells return edit; 9943cb98950SDavid Howells 9953cb98950SDavid Howells case assoc_array_walk_found_terminal_node: 9963cb98950SDavid Howells /* We found a node that doesn't have a node/shortcut pointer in 9973cb98950SDavid Howells * the slot corresponding to the index key that we have to 9983cb98950SDavid Howells * follow. 9993cb98950SDavid Howells */ 10003cb98950SDavid Howells if (!assoc_array_insert_into_terminal_node(edit, ops, index_key, 10013cb98950SDavid Howells &result)) 10023cb98950SDavid Howells goto enomem; 10033cb98950SDavid Howells return edit; 10043cb98950SDavid Howells 10053cb98950SDavid Howells case assoc_array_walk_found_wrong_shortcut: 10063cb98950SDavid Howells /* We found a shortcut that didn't match our key in a slot we 10073cb98950SDavid Howells * needed to follow. 10083cb98950SDavid Howells */ 10093cb98950SDavid Howells if (!assoc_array_insert_mid_shortcut(edit, ops, &result)) 10103cb98950SDavid Howells goto enomem; 10113cb98950SDavid Howells return edit; 10123cb98950SDavid Howells } 10133cb98950SDavid Howells 10143cb98950SDavid Howells enomem: 10153cb98950SDavid Howells /* Clean up after an out of memory error */ 10163cb98950SDavid Howells pr_devel("enomem\n"); 10173cb98950SDavid Howells assoc_array_cancel_edit(edit); 10183cb98950SDavid Howells return ERR_PTR(-ENOMEM); 10193cb98950SDavid Howells } 10203cb98950SDavid Howells 10213cb98950SDavid Howells /** 10223cb98950SDavid Howells * assoc_array_insert_set_object - Set the new object pointer in an edit script 10233cb98950SDavid Howells * @edit: The edit script to modify. 10243cb98950SDavid Howells * @object: The object pointer to set. 10253cb98950SDavid Howells * 10263cb98950SDavid Howells * Change the object to be inserted in an edit script. The object pointed to 10273cb98950SDavid Howells * by the old object is not freed. This must be done prior to applying the 10283cb98950SDavid Howells * script. 10293cb98950SDavid Howells */ 10303cb98950SDavid Howells void assoc_array_insert_set_object(struct assoc_array_edit *edit, void *object) 10313cb98950SDavid Howells { 10323cb98950SDavid Howells BUG_ON(!object); 10333cb98950SDavid Howells edit->leaf = assoc_array_leaf_to_ptr(object); 10343cb98950SDavid Howells } 10353cb98950SDavid Howells 10363cb98950SDavid Howells struct assoc_array_delete_collapse_context { 10373cb98950SDavid Howells struct assoc_array_node *node; 10383cb98950SDavid Howells const void *skip_leaf; 10393cb98950SDavid Howells int slot; 10403cb98950SDavid Howells }; 10413cb98950SDavid Howells 10423cb98950SDavid Howells /* 10433cb98950SDavid Howells * Subtree collapse to node iterator. 10443cb98950SDavid Howells */ 10453cb98950SDavid Howells static int assoc_array_delete_collapse_iterator(const void *leaf, 10463cb98950SDavid Howells void *iterator_data) 10473cb98950SDavid Howells { 10483cb98950SDavid Howells struct assoc_array_delete_collapse_context *collapse = iterator_data; 10493cb98950SDavid Howells 10503cb98950SDavid Howells if (leaf == collapse->skip_leaf) 10513cb98950SDavid Howells return 0; 10523cb98950SDavid Howells 10533cb98950SDavid Howells BUG_ON(collapse->slot >= ASSOC_ARRAY_FAN_OUT); 10543cb98950SDavid Howells 10553cb98950SDavid Howells collapse->node->slots[collapse->slot++] = assoc_array_leaf_to_ptr(leaf); 10563cb98950SDavid Howells return 0; 10573cb98950SDavid Howells } 10583cb98950SDavid Howells 10593cb98950SDavid Howells /** 10603cb98950SDavid Howells * assoc_array_delete - Script deletion of an object from an associative array 10613cb98950SDavid Howells * @array: The array to search. 10623cb98950SDavid Howells * @ops: The operations to use. 10633cb98950SDavid Howells * @index_key: The key to the object. 10643cb98950SDavid Howells * 10653cb98950SDavid Howells * Precalculate and preallocate a script for the deletion of an object from an 10663cb98950SDavid Howells * associative array. This results in an edit script that can either be 10673cb98950SDavid Howells * applied or cancelled. 10683cb98950SDavid Howells * 10693cb98950SDavid Howells * The function returns a pointer to an edit script if the object was found, 10703cb98950SDavid Howells * NULL if the object was not found or -ENOMEM. 10713cb98950SDavid Howells * 10723cb98950SDavid Howells * The caller should lock against other modifications and must continue to hold 10733cb98950SDavid Howells * the lock until assoc_array_apply_edit() has been called. 10743cb98950SDavid Howells * 10753cb98950SDavid Howells * Accesses to the tree may take place concurrently with this function, 10763cb98950SDavid Howells * provided they hold the RCU read lock. 10773cb98950SDavid Howells */ 10783cb98950SDavid Howells struct assoc_array_edit *assoc_array_delete(struct assoc_array *array, 10793cb98950SDavid Howells const struct assoc_array_ops *ops, 10803cb98950SDavid Howells const void *index_key) 10813cb98950SDavid Howells { 10823cb98950SDavid Howells struct assoc_array_delete_collapse_context collapse; 10833cb98950SDavid Howells struct assoc_array_walk_result result; 10843cb98950SDavid Howells struct assoc_array_node *node, *new_n0; 10853cb98950SDavid Howells struct assoc_array_edit *edit; 10863cb98950SDavid Howells struct assoc_array_ptr *ptr; 10873cb98950SDavid Howells bool has_meta; 10883cb98950SDavid Howells int slot, i; 10893cb98950SDavid Howells 10903cb98950SDavid Howells pr_devel("-->%s()\n", __func__); 10913cb98950SDavid Howells 10923cb98950SDavid Howells edit = kzalloc(sizeof(struct assoc_array_edit), GFP_KERNEL); 10933cb98950SDavid Howells if (!edit) 10943cb98950SDavid Howells return ERR_PTR(-ENOMEM); 10953cb98950SDavid Howells edit->array = array; 10963cb98950SDavid Howells edit->ops = ops; 10973cb98950SDavid Howells edit->adjust_count_by = -1; 10983cb98950SDavid Howells 10993cb98950SDavid Howells switch (assoc_array_walk(array, ops, index_key, &result)) { 11003cb98950SDavid Howells case assoc_array_walk_found_terminal_node: 11013cb98950SDavid Howells /* We found a node that should contain the leaf we've been 11023cb98950SDavid Howells * asked to remove - *if* it's in the tree. 11033cb98950SDavid Howells */ 11043cb98950SDavid Howells pr_devel("terminal_node\n"); 11053cb98950SDavid Howells node = result.terminal_node.node; 11063cb98950SDavid Howells 11073cb98950SDavid Howells for (slot = 0; slot < ASSOC_ARRAY_FAN_OUT; slot++) { 11083cb98950SDavid Howells ptr = node->slots[slot]; 11093cb98950SDavid Howells if (ptr && 11103cb98950SDavid Howells assoc_array_ptr_is_leaf(ptr) && 11113cb98950SDavid Howells ops->compare_object(assoc_array_ptr_to_leaf(ptr), 11123cb98950SDavid Howells index_key)) 11133cb98950SDavid Howells goto found_leaf; 11143cb98950SDavid Howells } 11154c1ca831SNick Desaulniers fallthrough; 11163cb98950SDavid Howells case assoc_array_walk_tree_empty: 11173cb98950SDavid Howells case assoc_array_walk_found_wrong_shortcut: 11183cb98950SDavid Howells default: 11193cb98950SDavid Howells assoc_array_cancel_edit(edit); 11203cb98950SDavid Howells pr_devel("not found\n"); 11213cb98950SDavid Howells return NULL; 11223cb98950SDavid Howells } 11233cb98950SDavid Howells 11243cb98950SDavid Howells found_leaf: 11253cb98950SDavid Howells BUG_ON(array->nr_leaves_on_tree <= 0); 11263cb98950SDavid Howells 11273cb98950SDavid Howells /* In the simplest form of deletion we just clear the slot and release 11283cb98950SDavid Howells * the leaf after a suitable interval. 11293cb98950SDavid Howells */ 11303cb98950SDavid Howells edit->dead_leaf = node->slots[slot]; 11313cb98950SDavid Howells edit->set[0].ptr = &node->slots[slot]; 11323cb98950SDavid Howells edit->set[0].to = NULL; 11333cb98950SDavid Howells edit->adjust_count_on = node; 11343cb98950SDavid Howells 11353cb98950SDavid Howells /* If that concludes erasure of the last leaf, then delete the entire 11363cb98950SDavid Howells * internal array. 11373cb98950SDavid Howells */ 11383cb98950SDavid Howells if (array->nr_leaves_on_tree == 1) { 11393cb98950SDavid Howells edit->set[1].ptr = &array->root; 11403cb98950SDavid Howells edit->set[1].to = NULL; 11413cb98950SDavid Howells edit->adjust_count_on = NULL; 11423cb98950SDavid Howells edit->excised_subtree = array->root; 11433cb98950SDavid Howells pr_devel("all gone\n"); 11443cb98950SDavid Howells return edit; 11453cb98950SDavid Howells } 11463cb98950SDavid Howells 11473cb98950SDavid Howells /* However, we'd also like to clear up some metadata blocks if we 11483cb98950SDavid Howells * possibly can. 11493cb98950SDavid Howells * 11503cb98950SDavid Howells * We go for a simple algorithm of: if this node has FAN_OUT or fewer 11513cb98950SDavid Howells * leaves in it, then attempt to collapse it - and attempt to 11523cb98950SDavid Howells * recursively collapse up the tree. 11533cb98950SDavid Howells * 11543cb98950SDavid Howells * We could also try and collapse in partially filled subtrees to take 11553cb98950SDavid Howells * up space in this node. 11563cb98950SDavid Howells */ 11573cb98950SDavid Howells if (node->nr_leaves_on_branch <= ASSOC_ARRAY_FAN_OUT + 1) { 11583cb98950SDavid Howells struct assoc_array_node *parent, *grandparent; 11593cb98950SDavid Howells struct assoc_array_ptr *ptr; 11603cb98950SDavid Howells 11613cb98950SDavid Howells /* First of all, we need to know if this node has metadata so 11623cb98950SDavid Howells * that we don't try collapsing if all the leaves are already 11633cb98950SDavid Howells * here. 11643cb98950SDavid Howells */ 11653cb98950SDavid Howells has_meta = false; 11663cb98950SDavid Howells for (i = 0; i < ASSOC_ARRAY_FAN_OUT; i++) { 11673cb98950SDavid Howells ptr = node->slots[i]; 11683cb98950SDavid Howells if (assoc_array_ptr_is_meta(ptr)) { 11693cb98950SDavid Howells has_meta = true; 11703cb98950SDavid Howells break; 11713cb98950SDavid Howells } 11723cb98950SDavid Howells } 11733cb98950SDavid Howells 11743cb98950SDavid Howells pr_devel("leaves: %ld [m=%d]\n", 11753cb98950SDavid Howells node->nr_leaves_on_branch - 1, has_meta); 11763cb98950SDavid Howells 11773cb98950SDavid Howells /* Look further up the tree to see if we can collapse this node 11783cb98950SDavid Howells * into a more proximal node too. 11793cb98950SDavid Howells */ 11803cb98950SDavid Howells parent = node; 11813cb98950SDavid Howells collapse_up: 11823cb98950SDavid Howells pr_devel("collapse subtree: %ld\n", parent->nr_leaves_on_branch); 11833cb98950SDavid Howells 11843cb98950SDavid Howells ptr = parent->back_pointer; 11853cb98950SDavid Howells if (!ptr) 11863cb98950SDavid Howells goto do_collapse; 11873cb98950SDavid Howells if (assoc_array_ptr_is_shortcut(ptr)) { 11883cb98950SDavid Howells struct assoc_array_shortcut *s = assoc_array_ptr_to_shortcut(ptr); 11893cb98950SDavid Howells ptr = s->back_pointer; 11903cb98950SDavid Howells if (!ptr) 11913cb98950SDavid Howells goto do_collapse; 11923cb98950SDavid Howells } 11933cb98950SDavid Howells 11943cb98950SDavid Howells grandparent = assoc_array_ptr_to_node(ptr); 11953cb98950SDavid Howells if (grandparent->nr_leaves_on_branch <= ASSOC_ARRAY_FAN_OUT + 1) { 11963cb98950SDavid Howells parent = grandparent; 11973cb98950SDavid Howells goto collapse_up; 11983cb98950SDavid Howells } 11993cb98950SDavid Howells 12003cb98950SDavid Howells do_collapse: 12013cb98950SDavid Howells /* There's no point collapsing if the original node has no meta 12023cb98950SDavid Howells * pointers to discard and if we didn't merge into one of that 12033cb98950SDavid Howells * node's ancestry. 12043cb98950SDavid Howells */ 12053cb98950SDavid Howells if (has_meta || parent != node) { 12063cb98950SDavid Howells node = parent; 12073cb98950SDavid Howells 12083cb98950SDavid Howells /* Create a new node to collapse into */ 12093cb98950SDavid Howells new_n0 = kzalloc(sizeof(struct assoc_array_node), GFP_KERNEL); 12103cb98950SDavid Howells if (!new_n0) 12113cb98950SDavid Howells goto enomem; 12123cb98950SDavid Howells edit->new_meta[0] = assoc_array_node_to_ptr(new_n0); 12133cb98950SDavid Howells 12143cb98950SDavid Howells new_n0->back_pointer = node->back_pointer; 12153cb98950SDavid Howells new_n0->parent_slot = node->parent_slot; 12163cb98950SDavid Howells new_n0->nr_leaves_on_branch = node->nr_leaves_on_branch; 12173cb98950SDavid Howells edit->adjust_count_on = new_n0; 12183cb98950SDavid Howells 12193cb98950SDavid Howells collapse.node = new_n0; 12203cb98950SDavid Howells collapse.skip_leaf = assoc_array_ptr_to_leaf(edit->dead_leaf); 12213cb98950SDavid Howells collapse.slot = 0; 12223cb98950SDavid Howells assoc_array_subtree_iterate(assoc_array_node_to_ptr(node), 12233cb98950SDavid Howells node->back_pointer, 12243cb98950SDavid Howells assoc_array_delete_collapse_iterator, 12253cb98950SDavid Howells &collapse); 12263cb98950SDavid Howells pr_devel("collapsed %d,%lu\n", collapse.slot, new_n0->nr_leaves_on_branch); 12273cb98950SDavid Howells BUG_ON(collapse.slot != new_n0->nr_leaves_on_branch - 1); 12283cb98950SDavid Howells 12293cb98950SDavid Howells if (!node->back_pointer) { 12303cb98950SDavid Howells edit->set[1].ptr = &array->root; 12313cb98950SDavid Howells } else if (assoc_array_ptr_is_leaf(node->back_pointer)) { 12323cb98950SDavid Howells BUG(); 12333cb98950SDavid Howells } else if (assoc_array_ptr_is_node(node->back_pointer)) { 12343cb98950SDavid Howells struct assoc_array_node *p = 12353cb98950SDavid Howells assoc_array_ptr_to_node(node->back_pointer); 12363cb98950SDavid Howells edit->set[1].ptr = &p->slots[node->parent_slot]; 12373cb98950SDavid Howells } else if (assoc_array_ptr_is_shortcut(node->back_pointer)) { 12383cb98950SDavid Howells struct assoc_array_shortcut *s = 12393cb98950SDavid Howells assoc_array_ptr_to_shortcut(node->back_pointer); 12403cb98950SDavid Howells edit->set[1].ptr = &s->next_node; 12413cb98950SDavid Howells } 12423cb98950SDavid Howells edit->set[1].to = assoc_array_node_to_ptr(new_n0); 12433cb98950SDavid Howells edit->excised_subtree = assoc_array_node_to_ptr(node); 12443cb98950SDavid Howells } 12453cb98950SDavid Howells } 12463cb98950SDavid Howells 12473cb98950SDavid Howells return edit; 12483cb98950SDavid Howells 12493cb98950SDavid Howells enomem: 12503cb98950SDavid Howells /* Clean up after an out of memory error */ 12513cb98950SDavid Howells pr_devel("enomem\n"); 12523cb98950SDavid Howells assoc_array_cancel_edit(edit); 12533cb98950SDavid Howells return ERR_PTR(-ENOMEM); 12543cb98950SDavid Howells } 12553cb98950SDavid Howells 12563cb98950SDavid Howells /** 12573cb98950SDavid Howells * assoc_array_clear - Script deletion of all objects from an associative array 12583cb98950SDavid Howells * @array: The array to clear. 12593cb98950SDavid Howells * @ops: The operations to use. 12603cb98950SDavid Howells * 12613cb98950SDavid Howells * Precalculate and preallocate a script for the deletion of all the objects 12623cb98950SDavid Howells * from an associative array. This results in an edit script that can either 12633cb98950SDavid Howells * be applied or cancelled. 12643cb98950SDavid Howells * 12653cb98950SDavid Howells * The function returns a pointer to an edit script if there are objects to be 12663cb98950SDavid Howells * deleted, NULL if there are no objects in the array or -ENOMEM. 12673cb98950SDavid Howells * 12683cb98950SDavid Howells * The caller should lock against other modifications and must continue to hold 12693cb98950SDavid Howells * the lock until assoc_array_apply_edit() has been called. 12703cb98950SDavid Howells * 12713cb98950SDavid Howells * Accesses to the tree may take place concurrently with this function, 12723cb98950SDavid Howells * provided they hold the RCU read lock. 12733cb98950SDavid Howells */ 12743cb98950SDavid Howells struct assoc_array_edit *assoc_array_clear(struct assoc_array *array, 12753cb98950SDavid Howells const struct assoc_array_ops *ops) 12763cb98950SDavid Howells { 12773cb98950SDavid Howells struct assoc_array_edit *edit; 12783cb98950SDavid Howells 12793cb98950SDavid Howells pr_devel("-->%s()\n", __func__); 12803cb98950SDavid Howells 12813cb98950SDavid Howells if (!array->root) 12823cb98950SDavid Howells return NULL; 12833cb98950SDavid Howells 12843cb98950SDavid Howells edit = kzalloc(sizeof(struct assoc_array_edit), GFP_KERNEL); 12853cb98950SDavid Howells if (!edit) 12863cb98950SDavid Howells return ERR_PTR(-ENOMEM); 12873cb98950SDavid Howells edit->array = array; 12883cb98950SDavid Howells edit->ops = ops; 12893cb98950SDavid Howells edit->set[1].ptr = &array->root; 12903cb98950SDavid Howells edit->set[1].to = NULL; 12913cb98950SDavid Howells edit->excised_subtree = array->root; 12923cb98950SDavid Howells edit->ops_for_excised_subtree = ops; 12933cb98950SDavid Howells pr_devel("all gone\n"); 12943cb98950SDavid Howells return edit; 12953cb98950SDavid Howells } 12963cb98950SDavid Howells 12973cb98950SDavid Howells /* 12983cb98950SDavid Howells * Handle the deferred destruction after an applied edit. 12993cb98950SDavid Howells */ 13003cb98950SDavid Howells static void assoc_array_rcu_cleanup(struct rcu_head *head) 13013cb98950SDavid Howells { 13023cb98950SDavid Howells struct assoc_array_edit *edit = 13033cb98950SDavid Howells container_of(head, struct assoc_array_edit, rcu); 13043cb98950SDavid Howells int i; 13053cb98950SDavid Howells 13063cb98950SDavid Howells pr_devel("-->%s()\n", __func__); 13073cb98950SDavid Howells 13083cb98950SDavid Howells if (edit->dead_leaf) 13093cb98950SDavid Howells edit->ops->free_object(assoc_array_ptr_to_leaf(edit->dead_leaf)); 13103cb98950SDavid Howells for (i = 0; i < ARRAY_SIZE(edit->excised_meta); i++) 13113cb98950SDavid Howells if (edit->excised_meta[i]) 13123cb98950SDavid Howells kfree(assoc_array_ptr_to_node(edit->excised_meta[i])); 13133cb98950SDavid Howells 13143cb98950SDavid Howells if (edit->excised_subtree) { 13153cb98950SDavid Howells BUG_ON(assoc_array_ptr_is_leaf(edit->excised_subtree)); 13163cb98950SDavid Howells if (assoc_array_ptr_is_node(edit->excised_subtree)) { 13173cb98950SDavid Howells struct assoc_array_node *n = 13183cb98950SDavid Howells assoc_array_ptr_to_node(edit->excised_subtree); 13193cb98950SDavid Howells n->back_pointer = NULL; 13203cb98950SDavid Howells } else { 13213cb98950SDavid Howells struct assoc_array_shortcut *s = 13223cb98950SDavid Howells assoc_array_ptr_to_shortcut(edit->excised_subtree); 13233cb98950SDavid Howells s->back_pointer = NULL; 13243cb98950SDavid Howells } 13253cb98950SDavid Howells assoc_array_destroy_subtree(edit->excised_subtree, 13263cb98950SDavid Howells edit->ops_for_excised_subtree); 13273cb98950SDavid Howells } 13283cb98950SDavid Howells 13293cb98950SDavid Howells kfree(edit); 13303cb98950SDavid Howells } 13313cb98950SDavid Howells 13323cb98950SDavid Howells /** 13333cb98950SDavid Howells * assoc_array_apply_edit - Apply an edit script to an associative array 13343cb98950SDavid Howells * @edit: The script to apply. 13353cb98950SDavid Howells * 13363cb98950SDavid Howells * Apply an edit script to an associative array to effect an insertion, 13373cb98950SDavid Howells * deletion or clearance. As the edit script includes preallocated memory, 13383cb98950SDavid Howells * this is guaranteed not to fail. 13393cb98950SDavid Howells * 13403cb98950SDavid Howells * The edit script, dead objects and dead metadata will be scheduled for 13413cb98950SDavid Howells * destruction after an RCU grace period to permit those doing read-only 13423cb98950SDavid Howells * accesses on the array to continue to do so under the RCU read lock whilst 13433cb98950SDavid Howells * the edit is taking place. 13443cb98950SDavid Howells */ 13453cb98950SDavid Howells void assoc_array_apply_edit(struct assoc_array_edit *edit) 13463cb98950SDavid Howells { 13473cb98950SDavid Howells struct assoc_array_shortcut *shortcut; 13483cb98950SDavid Howells struct assoc_array_node *node; 13493cb98950SDavid Howells struct assoc_array_ptr *ptr; 13503cb98950SDavid Howells int i; 13513cb98950SDavid Howells 13523cb98950SDavid Howells pr_devel("-->%s()\n", __func__); 13533cb98950SDavid Howells 13543cb98950SDavid Howells smp_wmb(); 13553cb98950SDavid Howells if (edit->leaf_p) 13563cb98950SDavid Howells *edit->leaf_p = edit->leaf; 13573cb98950SDavid Howells 13583cb98950SDavid Howells smp_wmb(); 13593cb98950SDavid Howells for (i = 0; i < ARRAY_SIZE(edit->set_parent_slot); i++) 13603cb98950SDavid Howells if (edit->set_parent_slot[i].p) 13613cb98950SDavid Howells *edit->set_parent_slot[i].p = edit->set_parent_slot[i].to; 13623cb98950SDavid Howells 13633cb98950SDavid Howells smp_wmb(); 13643cb98950SDavid Howells for (i = 0; i < ARRAY_SIZE(edit->set_backpointers); i++) 13653cb98950SDavid Howells if (edit->set_backpointers[i]) 13663cb98950SDavid Howells *edit->set_backpointers[i] = edit->set_backpointers_to; 13673cb98950SDavid Howells 13683cb98950SDavid Howells smp_wmb(); 13693cb98950SDavid Howells for (i = 0; i < ARRAY_SIZE(edit->set); i++) 13703cb98950SDavid Howells if (edit->set[i].ptr) 13713cb98950SDavid Howells *edit->set[i].ptr = edit->set[i].to; 13723cb98950SDavid Howells 13733cb98950SDavid Howells if (edit->array->root == NULL) { 13743cb98950SDavid Howells edit->array->nr_leaves_on_tree = 0; 13753cb98950SDavid Howells } else if (edit->adjust_count_on) { 13763cb98950SDavid Howells node = edit->adjust_count_on; 13773cb98950SDavid Howells for (;;) { 13783cb98950SDavid Howells node->nr_leaves_on_branch += edit->adjust_count_by; 13793cb98950SDavid Howells 13803cb98950SDavid Howells ptr = node->back_pointer; 13813cb98950SDavid Howells if (!ptr) 13823cb98950SDavid Howells break; 13833cb98950SDavid Howells if (assoc_array_ptr_is_shortcut(ptr)) { 13843cb98950SDavid Howells shortcut = assoc_array_ptr_to_shortcut(ptr); 13853cb98950SDavid Howells ptr = shortcut->back_pointer; 13863cb98950SDavid Howells if (!ptr) 13873cb98950SDavid Howells break; 13883cb98950SDavid Howells } 13893cb98950SDavid Howells BUG_ON(!assoc_array_ptr_is_node(ptr)); 13903cb98950SDavid Howells node = assoc_array_ptr_to_node(ptr); 13913cb98950SDavid Howells } 13923cb98950SDavid Howells 13933cb98950SDavid Howells edit->array->nr_leaves_on_tree += edit->adjust_count_by; 13943cb98950SDavid Howells } 13953cb98950SDavid Howells 13963cb98950SDavid Howells call_rcu(&edit->rcu, assoc_array_rcu_cleanup); 13973cb98950SDavid Howells } 13983cb98950SDavid Howells 13993cb98950SDavid Howells /** 14003cb98950SDavid Howells * assoc_array_cancel_edit - Discard an edit script. 14013cb98950SDavid Howells * @edit: The script to discard. 14023cb98950SDavid Howells * 14033cb98950SDavid Howells * Free an edit script and all the preallocated data it holds without making 14043cb98950SDavid Howells * any changes to the associative array it was intended for. 14053cb98950SDavid Howells * 14063cb98950SDavid Howells * NOTE! In the case of an insertion script, this does _not_ release the leaf 14073cb98950SDavid Howells * that was to be inserted. That is left to the caller. 14083cb98950SDavid Howells */ 14093cb98950SDavid Howells void assoc_array_cancel_edit(struct assoc_array_edit *edit) 14103cb98950SDavid Howells { 14113cb98950SDavid Howells struct assoc_array_ptr *ptr; 14123cb98950SDavid Howells int i; 14133cb98950SDavid Howells 14143cb98950SDavid Howells pr_devel("-->%s()\n", __func__); 14153cb98950SDavid Howells 14163cb98950SDavid Howells /* Clean up after an out of memory error */ 14173cb98950SDavid Howells for (i = 0; i < ARRAY_SIZE(edit->new_meta); i++) { 14183cb98950SDavid Howells ptr = edit->new_meta[i]; 14193cb98950SDavid Howells if (ptr) { 14203cb98950SDavid Howells if (assoc_array_ptr_is_node(ptr)) 14213cb98950SDavid Howells kfree(assoc_array_ptr_to_node(ptr)); 14223cb98950SDavid Howells else 14233cb98950SDavid Howells kfree(assoc_array_ptr_to_shortcut(ptr)); 14243cb98950SDavid Howells } 14253cb98950SDavid Howells } 14263cb98950SDavid Howells kfree(edit); 14273cb98950SDavid Howells } 14283cb98950SDavid Howells 14293cb98950SDavid Howells /** 14303cb98950SDavid Howells * assoc_array_gc - Garbage collect an associative array. 14313cb98950SDavid Howells * @array: The array to clean. 14323cb98950SDavid Howells * @ops: The operations to use. 14333cb98950SDavid Howells * @iterator: A callback function to pass judgement on each object. 14343cb98950SDavid Howells * @iterator_data: Private data for the callback function. 14353cb98950SDavid Howells * 14363cb98950SDavid Howells * Collect garbage from an associative array and pack down the internal tree to 14373cb98950SDavid Howells * save memory. 14383cb98950SDavid Howells * 14393cb98950SDavid Howells * The iterator function is asked to pass judgement upon each object in the 14403cb98950SDavid Howells * array. If it returns false, the object is discard and if it returns true, 14413cb98950SDavid Howells * the object is kept. If it returns true, it must increment the object's 14423cb98950SDavid Howells * usage count (or whatever it needs to do to retain it) before returning. 14433cb98950SDavid Howells * 14443cb98950SDavid Howells * This function returns 0 if successful or -ENOMEM if out of memory. In the 14453cb98950SDavid Howells * latter case, the array is not changed. 14463cb98950SDavid Howells * 14473cb98950SDavid Howells * The caller should lock against other modifications and must continue to hold 14483cb98950SDavid Howells * the lock until assoc_array_apply_edit() has been called. 14493cb98950SDavid Howells * 14503cb98950SDavid Howells * Accesses to the tree may take place concurrently with this function, 14513cb98950SDavid Howells * provided they hold the RCU read lock. 14523cb98950SDavid Howells */ 14533cb98950SDavid Howells int assoc_array_gc(struct assoc_array *array, 14543cb98950SDavid Howells const struct assoc_array_ops *ops, 14553cb98950SDavid Howells bool (*iterator)(void *object, void *iterator_data), 14563cb98950SDavid Howells void *iterator_data) 14573cb98950SDavid Howells { 14583cb98950SDavid Howells struct assoc_array_shortcut *shortcut, *new_s; 14593cb98950SDavid Howells struct assoc_array_node *node, *new_n; 14603cb98950SDavid Howells struct assoc_array_edit *edit; 14613cb98950SDavid Howells struct assoc_array_ptr *cursor, *ptr; 14623cb98950SDavid Howells struct assoc_array_ptr *new_root, *new_parent, **new_ptr_pp; 14633cb98950SDavid Howells unsigned long nr_leaves_on_tree; 14643cb98950SDavid Howells int keylen, slot, nr_free, next_slot, i; 14653cb98950SDavid Howells 14663cb98950SDavid Howells pr_devel("-->%s()\n", __func__); 14673cb98950SDavid Howells 14683cb98950SDavid Howells if (!array->root) 14693cb98950SDavid Howells return 0; 14703cb98950SDavid Howells 14713cb98950SDavid Howells edit = kzalloc(sizeof(struct assoc_array_edit), GFP_KERNEL); 14723cb98950SDavid Howells if (!edit) 14733cb98950SDavid Howells return -ENOMEM; 14743cb98950SDavid Howells edit->array = array; 14753cb98950SDavid Howells edit->ops = ops; 14763cb98950SDavid Howells edit->ops_for_excised_subtree = ops; 14773cb98950SDavid Howells edit->set[0].ptr = &array->root; 14783cb98950SDavid Howells edit->excised_subtree = array->root; 14793cb98950SDavid Howells 14803cb98950SDavid Howells new_root = new_parent = NULL; 14813cb98950SDavid Howells new_ptr_pp = &new_root; 14823cb98950SDavid Howells cursor = array->root; 14833cb98950SDavid Howells 14843cb98950SDavid Howells descend: 14853cb98950SDavid Howells /* If this point is a shortcut, then we need to duplicate it and 14863cb98950SDavid Howells * advance the target cursor. 14873cb98950SDavid Howells */ 14883cb98950SDavid Howells if (assoc_array_ptr_is_shortcut(cursor)) { 14893cb98950SDavid Howells shortcut = assoc_array_ptr_to_shortcut(cursor); 14903cb98950SDavid Howells keylen = round_up(shortcut->skip_to_level, ASSOC_ARRAY_KEY_CHUNK_SIZE); 14913cb98950SDavid Howells keylen >>= ASSOC_ARRAY_KEY_CHUNK_SHIFT; 1492*2a12e000SLen Baker new_s = kmalloc(struct_size(new_s, index_key, keylen), 1493*2a12e000SLen Baker GFP_KERNEL); 14943cb98950SDavid Howells if (!new_s) 14953cb98950SDavid Howells goto enomem; 14963cb98950SDavid Howells pr_devel("dup shortcut %p -> %p\n", shortcut, new_s); 1497*2a12e000SLen Baker memcpy(new_s, shortcut, struct_size(new_s, index_key, keylen)); 14983cb98950SDavid Howells new_s->back_pointer = new_parent; 14993cb98950SDavid Howells new_s->parent_slot = shortcut->parent_slot; 15003cb98950SDavid Howells *new_ptr_pp = new_parent = assoc_array_shortcut_to_ptr(new_s); 15013cb98950SDavid Howells new_ptr_pp = &new_s->next_node; 15023cb98950SDavid Howells cursor = shortcut->next_node; 15033cb98950SDavid Howells } 15043cb98950SDavid Howells 15053cb98950SDavid Howells /* Duplicate the node at this position */ 15063cb98950SDavid Howells node = assoc_array_ptr_to_node(cursor); 15073cb98950SDavid Howells new_n = kzalloc(sizeof(struct assoc_array_node), GFP_KERNEL); 15083cb98950SDavid Howells if (!new_n) 15093cb98950SDavid Howells goto enomem; 15103cb98950SDavid Howells pr_devel("dup node %p -> %p\n", node, new_n); 15113cb98950SDavid Howells new_n->back_pointer = new_parent; 15123cb98950SDavid Howells new_n->parent_slot = node->parent_slot; 15133cb98950SDavid Howells *new_ptr_pp = new_parent = assoc_array_node_to_ptr(new_n); 15143cb98950SDavid Howells new_ptr_pp = NULL; 15153cb98950SDavid Howells slot = 0; 15163cb98950SDavid Howells 15173cb98950SDavid Howells continue_node: 15183cb98950SDavid Howells /* Filter across any leaves and gc any subtrees */ 15193cb98950SDavid Howells for (; slot < ASSOC_ARRAY_FAN_OUT; slot++) { 15203cb98950SDavid Howells ptr = node->slots[slot]; 15213cb98950SDavid Howells if (!ptr) 15223cb98950SDavid Howells continue; 15233cb98950SDavid Howells 15243cb98950SDavid Howells if (assoc_array_ptr_is_leaf(ptr)) { 15253cb98950SDavid Howells if (iterator(assoc_array_ptr_to_leaf(ptr), 15263cb98950SDavid Howells iterator_data)) 15273cb98950SDavid Howells /* The iterator will have done any reference 15283cb98950SDavid Howells * counting on the object for us. 15293cb98950SDavid Howells */ 15303cb98950SDavid Howells new_n->slots[slot] = ptr; 15313cb98950SDavid Howells continue; 15323cb98950SDavid Howells } 15333cb98950SDavid Howells 15343cb98950SDavid Howells new_ptr_pp = &new_n->slots[slot]; 15353cb98950SDavid Howells cursor = ptr; 15363cb98950SDavid Howells goto descend; 15373cb98950SDavid Howells } 15383cb98950SDavid Howells 15393cb98950SDavid Howells pr_devel("-- compress node %p --\n", new_n); 15403cb98950SDavid Howells 15413cb98950SDavid Howells /* Count up the number of empty slots in this node and work out the 15423cb98950SDavid Howells * subtree leaf count. 15433cb98950SDavid Howells */ 15443cb98950SDavid Howells new_n->nr_leaves_on_branch = 0; 15453cb98950SDavid Howells nr_free = 0; 15463cb98950SDavid Howells for (slot = 0; slot < ASSOC_ARRAY_FAN_OUT; slot++) { 15473cb98950SDavid Howells ptr = new_n->slots[slot]; 15483cb98950SDavid Howells if (!ptr) 15493cb98950SDavid Howells nr_free++; 15503cb98950SDavid Howells else if (assoc_array_ptr_is_leaf(ptr)) 15513cb98950SDavid Howells new_n->nr_leaves_on_branch++; 15523cb98950SDavid Howells } 15533cb98950SDavid Howells pr_devel("free=%d, leaves=%lu\n", nr_free, new_n->nr_leaves_on_branch); 15543cb98950SDavid Howells 15553cb98950SDavid Howells /* See what we can fold in */ 15563cb98950SDavid Howells next_slot = 0; 15573cb98950SDavid Howells for (slot = 0; slot < ASSOC_ARRAY_FAN_OUT; slot++) { 15583cb98950SDavid Howells struct assoc_array_shortcut *s; 15593cb98950SDavid Howells struct assoc_array_node *child; 15603cb98950SDavid Howells 15613cb98950SDavid Howells ptr = new_n->slots[slot]; 15623cb98950SDavid Howells if (!ptr || assoc_array_ptr_is_leaf(ptr)) 15633cb98950SDavid Howells continue; 15643cb98950SDavid Howells 15653cb98950SDavid Howells s = NULL; 15663cb98950SDavid Howells if (assoc_array_ptr_is_shortcut(ptr)) { 15673cb98950SDavid Howells s = assoc_array_ptr_to_shortcut(ptr); 15683cb98950SDavid Howells ptr = s->next_node; 15693cb98950SDavid Howells } 15703cb98950SDavid Howells 15713cb98950SDavid Howells child = assoc_array_ptr_to_node(ptr); 15723cb98950SDavid Howells new_n->nr_leaves_on_branch += child->nr_leaves_on_branch; 15733cb98950SDavid Howells 15743cb98950SDavid Howells if (child->nr_leaves_on_branch <= nr_free + 1) { 15753cb98950SDavid Howells /* Fold the child node into this one */ 15763cb98950SDavid Howells pr_devel("[%d] fold node %lu/%d [nx %d]\n", 15773cb98950SDavid Howells slot, child->nr_leaves_on_branch, nr_free + 1, 15783cb98950SDavid Howells next_slot); 15793cb98950SDavid Howells 15803cb98950SDavid Howells /* We would already have reaped an intervening shortcut 15813cb98950SDavid Howells * on the way back up the tree. 15823cb98950SDavid Howells */ 15833cb98950SDavid Howells BUG_ON(s); 15843cb98950SDavid Howells 15853cb98950SDavid Howells new_n->slots[slot] = NULL; 15863cb98950SDavid Howells nr_free++; 15873cb98950SDavid Howells if (slot < next_slot) 15883cb98950SDavid Howells next_slot = slot; 15893cb98950SDavid Howells for (i = 0; i < ASSOC_ARRAY_FAN_OUT; i++) { 15903cb98950SDavid Howells struct assoc_array_ptr *p = child->slots[i]; 15913cb98950SDavid Howells if (!p) 15923cb98950SDavid Howells continue; 15933cb98950SDavid Howells BUG_ON(assoc_array_ptr_is_meta(p)); 15943cb98950SDavid Howells while (new_n->slots[next_slot]) 15953cb98950SDavid Howells next_slot++; 15963cb98950SDavid Howells BUG_ON(next_slot >= ASSOC_ARRAY_FAN_OUT); 15973cb98950SDavid Howells new_n->slots[next_slot++] = p; 15983cb98950SDavid Howells nr_free--; 15993cb98950SDavid Howells } 16003cb98950SDavid Howells kfree(child); 16013cb98950SDavid Howells } else { 16023cb98950SDavid Howells pr_devel("[%d] retain node %lu/%d [nx %d]\n", 16033cb98950SDavid Howells slot, child->nr_leaves_on_branch, nr_free + 1, 16043cb98950SDavid Howells next_slot); 16053cb98950SDavid Howells } 16063cb98950SDavid Howells } 16073cb98950SDavid Howells 16083cb98950SDavid Howells pr_devel("after: %lu\n", new_n->nr_leaves_on_branch); 16093cb98950SDavid Howells 16103cb98950SDavid Howells nr_leaves_on_tree = new_n->nr_leaves_on_branch; 16113cb98950SDavid Howells 16123cb98950SDavid Howells /* Excise this node if it is singly occupied by a shortcut */ 16133cb98950SDavid Howells if (nr_free == ASSOC_ARRAY_FAN_OUT - 1) { 16143cb98950SDavid Howells for (slot = 0; slot < ASSOC_ARRAY_FAN_OUT; slot++) 16153cb98950SDavid Howells if ((ptr = new_n->slots[slot])) 16163cb98950SDavid Howells break; 16173cb98950SDavid Howells 16183cb98950SDavid Howells if (assoc_array_ptr_is_meta(ptr) && 16193cb98950SDavid Howells assoc_array_ptr_is_shortcut(ptr)) { 16203cb98950SDavid Howells pr_devel("excise node %p with 1 shortcut\n", new_n); 16213cb98950SDavid Howells new_s = assoc_array_ptr_to_shortcut(ptr); 16223cb98950SDavid Howells new_parent = new_n->back_pointer; 16233cb98950SDavid Howells slot = new_n->parent_slot; 16243cb98950SDavid Howells kfree(new_n); 16253cb98950SDavid Howells if (!new_parent) { 16263cb98950SDavid Howells new_s->back_pointer = NULL; 16273cb98950SDavid Howells new_s->parent_slot = 0; 16283cb98950SDavid Howells new_root = ptr; 16293cb98950SDavid Howells goto gc_complete; 16303cb98950SDavid Howells } 16313cb98950SDavid Howells 16323cb98950SDavid Howells if (assoc_array_ptr_is_shortcut(new_parent)) { 16333cb98950SDavid Howells /* We can discard any preceding shortcut also */ 16343cb98950SDavid Howells struct assoc_array_shortcut *s = 16353cb98950SDavid Howells assoc_array_ptr_to_shortcut(new_parent); 16363cb98950SDavid Howells 16373cb98950SDavid Howells pr_devel("excise preceding shortcut\n"); 16383cb98950SDavid Howells 16393cb98950SDavid Howells new_parent = new_s->back_pointer = s->back_pointer; 16403cb98950SDavid Howells slot = new_s->parent_slot = s->parent_slot; 16413cb98950SDavid Howells kfree(s); 16423cb98950SDavid Howells if (!new_parent) { 16433cb98950SDavid Howells new_s->back_pointer = NULL; 16443cb98950SDavid Howells new_s->parent_slot = 0; 16453cb98950SDavid Howells new_root = ptr; 16463cb98950SDavid Howells goto gc_complete; 16473cb98950SDavid Howells } 16483cb98950SDavid Howells } 16493cb98950SDavid Howells 16503cb98950SDavid Howells new_s->back_pointer = new_parent; 16513cb98950SDavid Howells new_s->parent_slot = slot; 16523cb98950SDavid Howells new_n = assoc_array_ptr_to_node(new_parent); 16533cb98950SDavid Howells new_n->slots[slot] = ptr; 16543cb98950SDavid Howells goto ascend_old_tree; 16553cb98950SDavid Howells } 16563cb98950SDavid Howells } 16573cb98950SDavid Howells 16583cb98950SDavid Howells /* Excise any shortcuts we might encounter that point to nodes that 16593cb98950SDavid Howells * only contain leaves. 16603cb98950SDavid Howells */ 16613cb98950SDavid Howells ptr = new_n->back_pointer; 16623cb98950SDavid Howells if (!ptr) 16633cb98950SDavid Howells goto gc_complete; 16643cb98950SDavid Howells 16653cb98950SDavid Howells if (assoc_array_ptr_is_shortcut(ptr)) { 16663cb98950SDavid Howells new_s = assoc_array_ptr_to_shortcut(ptr); 16673cb98950SDavid Howells new_parent = new_s->back_pointer; 16683cb98950SDavid Howells slot = new_s->parent_slot; 16693cb98950SDavid Howells 16703cb98950SDavid Howells if (new_n->nr_leaves_on_branch <= ASSOC_ARRAY_FAN_OUT) { 16713cb98950SDavid Howells struct assoc_array_node *n; 16723cb98950SDavid Howells 16733cb98950SDavid Howells pr_devel("excise shortcut\n"); 16743cb98950SDavid Howells new_n->back_pointer = new_parent; 16753cb98950SDavid Howells new_n->parent_slot = slot; 16763cb98950SDavid Howells kfree(new_s); 16773cb98950SDavid Howells if (!new_parent) { 16783cb98950SDavid Howells new_root = assoc_array_node_to_ptr(new_n); 16793cb98950SDavid Howells goto gc_complete; 16803cb98950SDavid Howells } 16813cb98950SDavid Howells 16823cb98950SDavid Howells n = assoc_array_ptr_to_node(new_parent); 16833cb98950SDavid Howells n->slots[slot] = assoc_array_node_to_ptr(new_n); 16843cb98950SDavid Howells } 16853cb98950SDavid Howells } else { 16863cb98950SDavid Howells new_parent = ptr; 16873cb98950SDavid Howells } 16883cb98950SDavid Howells new_n = assoc_array_ptr_to_node(new_parent); 16893cb98950SDavid Howells 16903cb98950SDavid Howells ascend_old_tree: 16913cb98950SDavid Howells ptr = node->back_pointer; 16923cb98950SDavid Howells if (assoc_array_ptr_is_shortcut(ptr)) { 16933cb98950SDavid Howells shortcut = assoc_array_ptr_to_shortcut(ptr); 16943cb98950SDavid Howells slot = shortcut->parent_slot; 16953cb98950SDavid Howells cursor = shortcut->back_pointer; 169695389b08SDavid Howells if (!cursor) 169795389b08SDavid Howells goto gc_complete; 16983cb98950SDavid Howells } else { 16993cb98950SDavid Howells slot = node->parent_slot; 17003cb98950SDavid Howells cursor = ptr; 17013cb98950SDavid Howells } 170295389b08SDavid Howells BUG_ON(!cursor); 17033cb98950SDavid Howells node = assoc_array_ptr_to_node(cursor); 17043cb98950SDavid Howells slot++; 17053cb98950SDavid Howells goto continue_node; 17063cb98950SDavid Howells 17073cb98950SDavid Howells gc_complete: 17083cb98950SDavid Howells edit->set[0].to = new_root; 17093cb98950SDavid Howells assoc_array_apply_edit(edit); 171027419604SDavid Howells array->nr_leaves_on_tree = nr_leaves_on_tree; 17113cb98950SDavid Howells return 0; 17123cb98950SDavid Howells 17133cb98950SDavid Howells enomem: 17143cb98950SDavid Howells pr_devel("enomem\n"); 17153cb98950SDavid Howells assoc_array_destroy_subtree(new_root, edit->ops); 17163cb98950SDavid Howells kfree(edit); 17173cb98950SDavid Howells return -ENOMEM; 17183cb98950SDavid Howells } 1719