diff options
Diffstat (limited to 'target/linux/generic/patches-3.18/080-20-fib_trie-Fix-RCU-bug-and-merge-similar-bits-of-infla.patch')
-rw-r--r-- | target/linux/generic/patches-3.18/080-20-fib_trie-Fix-RCU-bug-and-merge-similar-bits-of-infla.patch | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/target/linux/generic/patches-3.18/080-20-fib_trie-Fix-RCU-bug-and-merge-similar-bits-of-infla.patch b/target/linux/generic/patches-3.18/080-20-fib_trie-Fix-RCU-bug-and-merge-similar-bits-of-infla.patch new file mode 100644 index 0000000..7e26127 --- /dev/null +++ b/target/linux/generic/patches-3.18/080-20-fib_trie-Fix-RCU-bug-and-merge-similar-bits-of-infla.patch @@ -0,0 +1,267 @@ +From: Alexander Duyck <alexander.h.duyck@redhat.com> +Date: Thu, 22 Jan 2015 15:51:14 -0800 +Subject: [PATCH] fib_trie: Fix RCU bug and merge similar bits of inflate/halve + +This patch addresses two issues. + +The first issue is the fact that I believe I had the RCU freeing sequence +slightly out of order. As a result we could get into an issue if a caller +went into a child of a child of the new node, then backtraced into the to be +freed parent, and then attempted to access a child of a child that may have +been consumed in a resize of one of the new nodes children. To resolve this I +have moved the resize after we have freed the oldtnode. The only side effect +of this is that we will now be calling resize on more nodes in the case of +inflate due to the fact that we don't have a good way to test to see if a +full_tnode on the new node was there before or after the allocation. This +should have minimal impact however since the node should already be +correctly size so it is just the cost of calling should_inflate that we +will be taking on the node which is only a couple of cycles. + +The second issue is the fact that inflate and halve were essentially doing +the same thing after the new node was added to the trie replacing the old +one. As such it wasn't really necessary to keep the code in both functions +so I have split it out into two other functions, called replace and +update_children. + +Signed-off-by: Alexander Duyck <alexander.h.duyck@redhat.com> +Signed-off-by: David S. Miller <davem@davemloft.net> +--- + +--- a/net/ipv4/fib_trie.c ++++ b/net/ipv4/fib_trie.c +@@ -396,8 +396,30 @@ static void put_child(struct tnode *tn, + rcu_assign_pointer(tn->child[i], n); + } + +-static void put_child_root(struct tnode *tp, struct trie *t, +- t_key key, struct tnode *n) ++static void update_children(struct tnode *tn) ++{ ++ unsigned long i; ++ ++ /* update all of the child parent pointers */ ++ for (i = tnode_child_length(tn); i;) { ++ struct tnode *inode = tnode_get_child(tn, --i); ++ ++ if (!inode) ++ continue; ++ ++ /* Either update the children of a tnode that ++ * already belongs to us or update the child ++ * to point to ourselves. ++ */ ++ if (node_parent(inode) == tn) ++ update_children(inode); ++ else ++ node_set_parent(inode, tn); ++ } ++} ++ ++static inline void put_child_root(struct tnode *tp, struct trie *t, ++ t_key key, struct tnode *n) + { + if (tp) + put_child(tp, get_index(key, tp), n); +@@ -434,10 +456,35 @@ static void tnode_free(struct tnode *tn) + } + } + ++static void replace(struct trie *t, struct tnode *oldtnode, struct tnode *tn) ++{ ++ struct tnode *tp = node_parent(oldtnode); ++ unsigned long i; ++ ++ /* setup the parent pointer out of and back into this node */ ++ NODE_INIT_PARENT(tn, tp); ++ put_child_root(tp, t, tn->key, tn); ++ ++ /* update all of the child parent pointers */ ++ update_children(tn); ++ ++ /* all pointers should be clean so we are done */ ++ tnode_free(oldtnode); ++ ++ /* resize children now that oldtnode is freed */ ++ for (i = tnode_child_length(tn); i;) { ++ struct tnode *inode = tnode_get_child(tn, --i); ++ ++ /* resize child node */ ++ if (tnode_full(tn, inode)) ++ resize(t, inode); ++ } ++} ++ + static int inflate(struct trie *t, struct tnode *oldtnode) + { +- struct tnode *inode, *node0, *node1, *tn, *tp; +- unsigned long i, j, k; ++ struct tnode *tn; ++ unsigned long i; + t_key m; + + pr_debug("In inflate\n"); +@@ -446,13 +493,18 @@ static int inflate(struct trie *t, struc + if (!tn) + return -ENOMEM; + ++ /* prepare oldtnode to be freed */ ++ tnode_free_init(oldtnode); ++ + /* Assemble all of the pointers in our cluster, in this case that + * represents all of the pointers out of our allocated nodes that + * point to existing tnodes and the links between our allocated + * nodes. + */ + for (i = tnode_child_length(oldtnode), m = 1u << tn->pos; i;) { +- inode = tnode_get_child(oldtnode, --i); ++ struct tnode *inode = tnode_get_child(oldtnode, --i); ++ struct tnode *node0, *node1; ++ unsigned long j, k; + + /* An empty child */ + if (inode == NULL) +@@ -464,6 +516,9 @@ static int inflate(struct trie *t, struc + continue; + } + ++ /* drop the node in the old tnode free list */ ++ tnode_free_append(oldtnode, inode); ++ + /* An internal node with two children */ + if (inode->bits == 1) { + put_child(tn, 2 * i + 1, tnode_get_child(inode, 1)); +@@ -488,9 +543,9 @@ static int inflate(struct trie *t, struc + node1 = tnode_new(inode->key | m, inode->pos, inode->bits - 1); + if (!node1) + goto nomem; +- tnode_free_append(tn, node1); ++ node0 = tnode_new(inode->key, inode->pos, inode->bits - 1); + +- node0 = tnode_new(inode->key & ~m, inode->pos, inode->bits - 1); ++ tnode_free_append(tn, node1); + if (!node0) + goto nomem; + tnode_free_append(tn, node0); +@@ -512,53 +567,9 @@ static int inflate(struct trie *t, struc + put_child(tn, 2 * i, node0); + } + +- /* setup the parent pointer into and out of this node */ +- tp = node_parent(oldtnode); +- NODE_INIT_PARENT(tn, tp); +- put_child_root(tp, t, tn->key, tn); ++ /* setup the parent pointers into and out of this node */ ++ replace(t, oldtnode, tn); + +- /* prepare oldtnode to be freed */ +- tnode_free_init(oldtnode); +- +- /* update all child nodes parent pointers to route to us */ +- for (i = tnode_child_length(oldtnode); i;) { +- inode = tnode_get_child(oldtnode, --i); +- +- /* A leaf or an internal node with skipped bits */ +- if (!tnode_full(oldtnode, inode)) { +- node_set_parent(inode, tn); +- continue; +- } +- +- /* drop the node in the old tnode free list */ +- tnode_free_append(oldtnode, inode); +- +- /* fetch new nodes */ +- node1 = tnode_get_child(tn, 2 * i + 1); +- node0 = tnode_get_child(tn, 2 * i); +- +- /* bits == 1 then node0 and node1 represent inode's children */ +- if (inode->bits == 1) { +- node_set_parent(node1, tn); +- node_set_parent(node0, tn); +- continue; +- } +- +- /* update parent pointers in child node's children */ +- for (k = tnode_child_length(inode), j = k / 2; j;) { +- node_set_parent(tnode_get_child(inode, --k), node1); +- node_set_parent(tnode_get_child(inode, --j), node0); +- node_set_parent(tnode_get_child(inode, --k), node1); +- node_set_parent(tnode_get_child(inode, --j), node0); +- } +- +- /* resize child nodes */ +- resize(t, node1); +- resize(t, node0); +- } +- +- /* we completed without error, prepare to free old node */ +- tnode_free(oldtnode); + return 0; + nomem: + /* all pointers should be clean so we are done */ +@@ -568,7 +579,7 @@ nomem: + + static int halve(struct trie *t, struct tnode *oldtnode) + { +- struct tnode *tn, *tp, *inode, *node0, *node1; ++ struct tnode *tn; + unsigned long i; + + pr_debug("In halve\n"); +@@ -577,14 +588,18 @@ static int halve(struct trie *t, struct + if (!tn) + return -ENOMEM; + ++ /* prepare oldtnode to be freed */ ++ tnode_free_init(oldtnode); ++ + /* Assemble all of the pointers in our cluster, in this case that + * represents all of the pointers out of our allocated nodes that + * point to existing tnodes and the links between our allocated + * nodes. + */ + for (i = tnode_child_length(oldtnode); i;) { +- node1 = tnode_get_child(oldtnode, --i); +- node0 = tnode_get_child(oldtnode, --i); ++ struct tnode *node1 = tnode_get_child(oldtnode, --i); ++ struct tnode *node0 = tnode_get_child(oldtnode, --i); ++ struct tnode *inode; + + /* At least one of the children is empty */ + if (!node1 || !node0) { +@@ -609,34 +624,8 @@ static int halve(struct trie *t, struct + put_child(tn, i / 2, inode); + } + +- /* setup the parent pointer out of and back into this node */ +- tp = node_parent(oldtnode); +- NODE_INIT_PARENT(tn, tp); +- put_child_root(tp, t, tn->key, tn); +- +- /* prepare oldtnode to be freed */ +- tnode_free_init(oldtnode); +- +- /* update all of the child parent pointers */ +- for (i = tnode_child_length(tn); i;) { +- inode = tnode_get_child(tn, --i); +- +- /* only new tnodes will be considered "full" nodes */ +- if (!tnode_full(tn, inode)) { +- node_set_parent(inode, tn); +- continue; +- } +- +- /* Two nonempty children */ +- node_set_parent(tnode_get_child(inode, 1), inode); +- node_set_parent(tnode_get_child(inode, 0), inode); +- +- /* resize child node */ +- resize(t, inode); +- } +- +- /* all pointers should be clean so we are done */ +- tnode_free(oldtnode); ++ /* setup the parent pointers into and out of this node */ ++ replace(t, oldtnode, tn); + + return 0; + } |