]> git.baikalelectronics.ru Git - kernel.git/commitdiff
net: sched: change tcf block reference counter type to refcount_t
authorVlad Buslov <vladbu@mellanox.com>
Mon, 24 Sep 2018 16:22:54 +0000 (19:22 +0300)
committerDavid S. Miller <davem@davemloft.net>
Wed, 26 Sep 2018 03:17:36 +0000 (20:17 -0700)
As a preparation for removing rtnl lock dependency from rules update path,
change tcf block reference counter type to refcount_t to allow modification
by concurrent users.

In block put function perform decrement and check reference counter once to
accommodate concurrent modification by unlocked users. After this change
tcf_chain_put at the end of block put function is called with
block->refcnt==0 and will deallocate block after the last chain is
released, so there is no need to manually deallocate block in this case.
However, if block reference counter reached 0 and there are no chains to
release, block must still be deallocated manually.

Signed-off-by: Vlad Buslov <vladbu@mellanox.com>
Acked-by: Jiri Pirko <jiri@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/net/sch_generic.h
net/sched/cls_api.c

index 43b17f82d8eebad5239a9b86a145aa9d556ef29c..4a86f4d33f0790d3dee3f3ada2e8fd82e4264768 100644 (file)
@@ -345,7 +345,7 @@ struct tcf_chain {
 struct tcf_block {
        struct list_head chain_list;
        u32 index; /* block index for shared blocks */
-       unsigned int refcnt;
+       refcount_t refcnt;
        struct net *net;
        struct Qdisc *q;
        struct list_head cb_list;
index c33636e7b43127c108f0fefdbe9032d3bbc7fe5c..90843b6a8fa941aec405d13daf8ba775a18be57d 100644 (file)
@@ -240,7 +240,7 @@ static void tcf_chain_destroy(struct tcf_chain *chain)
        if (!chain->index)
                block->chain0.chain = NULL;
        kfree(chain);
-       if (list_empty(&block->chain_list) && block->refcnt == 0)
+       if (list_empty(&block->chain_list) && !refcount_read(&block->refcnt))
                kfree(block);
 }
 
@@ -510,7 +510,7 @@ static struct tcf_block *tcf_block_create(struct net *net, struct Qdisc *q,
        INIT_LIST_HEAD(&block->owner_list);
        INIT_LIST_HEAD(&block->chain0.filter_chain_list);
 
-       block->refcnt = 1;
+       refcount_set(&block->refcnt, 1);
        block->net = net;
        block->index = block_index;
 
@@ -710,7 +710,7 @@ int tcf_block_get_ext(struct tcf_block **p_block, struct Qdisc *q,
                /* block_index not 0 means the shared block is requested */
                block = tcf_block_lookup(net, ei->block_index);
                if (block)
-                       block->refcnt++;
+                       refcount_inc(&block->refcnt);
        }
 
        if (!block) {
@@ -753,7 +753,7 @@ err_block_owner_add:
 err_block_insert:
                kfree(block);
        } else {
-               block->refcnt--;
+               refcount_dec(&block->refcnt);
        }
        return err;
 }
@@ -793,34 +793,45 @@ void tcf_block_put_ext(struct tcf_block *block, struct Qdisc *q,
        tcf_chain0_head_change_cb_del(block, ei);
        tcf_block_owner_del(block, q, ei->binder_type);
 
-       if (block->refcnt == 1) {
-               if (tcf_block_shared(block))
-                       tcf_block_remove(block, block->net);
-
-               /* Hold a refcnt for all chains, so that they don't disappear
-                * while we are iterating.
+       if (refcount_dec_and_test(&block->refcnt)) {
+               /* Flushing/putting all chains will cause the block to be
+                * deallocated when last chain is freed. However, if chain_list
+                * is empty, block has to be manually deallocated. After block
+                * reference counter reached 0, it is no longer possible to
+                * increment it or add new chains to block.
                 */
-               list_for_each_entry(chain, &block->chain_list, list)
-                       tcf_chain_hold(chain);
+               bool free_block = list_empty(&block->chain_list);
 
-               list_for_each_entry(chain, &block->chain_list, list)
-                       tcf_chain_flush(chain);
-       }
+               if (tcf_block_shared(block))
+                       tcf_block_remove(block, block->net);
 
-       tcf_block_offload_unbind(block, q, ei);
+               if (!free_block) {
+                       /* Hold a refcnt for all chains, so that they don't
+                        * disappear while we are iterating.
+                        */
+                       list_for_each_entry(chain, &block->chain_list, list)
+                               tcf_chain_hold(chain);
 
-       if (block->refcnt == 1) {
-               /* At this point, all the chains should have refcnt >= 1. */
-               list_for_each_entry_safe(chain, tmp, &block->chain_list, list) {
-                       tcf_chain_put_explicitly_created(chain);
-                       tcf_chain_put(chain);
+                       list_for_each_entry(chain, &block->chain_list, list)
+                               tcf_chain_flush(chain);
                }
 
-               block->refcnt--;
-               if (list_empty(&block->chain_list))
+               tcf_block_offload_unbind(block, q, ei);
+
+               if (free_block) {
                        kfree(block);
+               } else {
+                       /* At this point, all the chains should have
+                        * refcnt >= 1.
+                        */
+                       list_for_each_entry_safe(chain, tmp, &block->chain_list,
+                                                list) {
+                               tcf_chain_put_explicitly_created(chain);
+                               tcf_chain_put(chain);
+                       }
+               }
        } else {
-               block->refcnt--;
+               tcf_block_offload_unbind(block, q, ei);
        }
 }
 EXPORT_SYMBOL(tcf_block_put_ext);