diff options
Diffstat (limited to 'net/sched/cls_u32.c')
-rw-r--r-- | net/sched/cls_u32.c | 35 |
1 files changed, 28 insertions, 7 deletions
diff --git a/net/sched/cls_u32.c b/net/sched/cls_u32.c index 0472909bb014..cab9e9b43967 100644 --- a/net/sched/cls_u32.c +++ b/net/sched/cls_u32.c @@ -78,8 +78,11 @@ struct tc_u_hnode { struct tc_u_common *tp_c; int refcnt; unsigned int divisor; - struct tc_u_knode __rcu *ht[1]; struct rcu_head rcu; + /* The 'ht' field MUST be the last field in structure to allow for + * more entries allocated at end of structure. + */ + struct tc_u_knode __rcu *ht[1]; }; struct tc_u_common { @@ -299,10 +302,6 @@ static unsigned long u32_get(struct tcf_proto *tp, u32 handle) return (unsigned long)u32_lookup_key(ht, handle); } -static void u32_put(struct tcf_proto *tp, unsigned long f) -{ -} - static u32 gen_new_htid(struct tc_u_common *tp_c) { int i = 0x800; @@ -464,13 +463,35 @@ static int u32_destroy_hnode(struct tcf_proto *tp, struct tc_u_hnode *ht) return -ENOENT; } -static void u32_destroy(struct tcf_proto *tp) +static bool ht_empty(struct tc_u_hnode *ht) +{ + unsigned int h; + + for (h = 0; h <= ht->divisor; h++) + if (rcu_access_pointer(ht->ht[h])) + return false; + + return true; +} + +static bool u32_destroy(struct tcf_proto *tp, bool force) { struct tc_u_common *tp_c = tp->data; struct tc_u_hnode *root_ht = rtnl_dereference(tp->root); WARN_ON(root_ht == NULL); + if (!force) { + if (root_ht) { + if (root_ht->refcnt > 1) + return false; + if (root_ht->refcnt == 1) { + if (!ht_empty(root_ht)) + return false; + } + } + } + if (root_ht && --root_ht->refcnt == 0) u32_destroy_hnode(tp, root_ht); @@ -495,6 +516,7 @@ static void u32_destroy(struct tcf_proto *tp) } tp->data = NULL; + return true; } static int u32_delete(struct tcf_proto *tp, unsigned long arg) @@ -1021,7 +1043,6 @@ static struct tcf_proto_ops cls_u32_ops __read_mostly = { .init = u32_init, .destroy = u32_destroy, .get = u32_get, - .put = u32_put, .change = u32_change, .delete = u32_delete, .walk = u32_walk, |