]> git.proxmox.com Git - mirror_ubuntu-hirsute-kernel.git/blobdiff - net/sched/cls_api.c
net: sched: store net pointer in block and introduce qdisc_net helper
[mirror_ubuntu-hirsute-kernel.git] / net / sched / cls_api.c
index ea6c65fd5fc5fa31669191470d963bc851822a00..856003caa3bbe8d2265517be4e994bd25e077606 100644 (file)
@@ -194,21 +194,20 @@ static void tcf_chain_flush(struct tcf_chain *chain)
                RCU_INIT_POINTER(*chain->p_filter_chain, NULL);
        while ((tp = rtnl_dereference(chain->filter_chain)) != NULL) {
                RCU_INIT_POINTER(chain->filter_chain, tp->next);
+               tcf_chain_put(chain);
                tcf_proto_destroy(tp);
        }
 }
 
 static void tcf_chain_destroy(struct tcf_chain *chain)
 {
-       /* May be already removed from the list by the previous call. */
-       if (!list_empty(&chain->list))
-               list_del_init(&chain->list);
+       list_del(&chain->list);
+       kfree(chain);
+}
 
-       /* There might still be a reference held when we got here from
-        * tcf_block_put. Wait for the user to drop reference before free.
-        */
-       if (!chain->refcnt)
-               kfree(chain);
+static void tcf_chain_hold(struct tcf_chain *chain)
+{
+       ++chain->refcnt;
 }
 
 struct tcf_chain *tcf_chain_get(struct tcf_block *block, u32 chain_index,
@@ -218,23 +217,18 @@ struct tcf_chain *tcf_chain_get(struct tcf_block *block, u32 chain_index,
 
        list_for_each_entry(chain, &block->chain_list, list) {
                if (chain->index == chain_index) {
-                       chain->refcnt++;
+                       tcf_chain_hold(chain);
                        return chain;
                }
        }
-       if (create)
-               return tcf_chain_create(block, chain_index);
-       else
-               return NULL;
+
+       return create ? tcf_chain_create(block, chain_index) : NULL;
 }
 EXPORT_SYMBOL(tcf_chain_get);
 
 void tcf_chain_put(struct tcf_chain *chain)
 {
-       /* Destroy unused chain, with exception of chain 0, which is the
-        * default one and has to be always present.
-        */
-       if (--chain->refcnt == 0 && !chain->filter_chain && chain->index != 0)
+       if (--chain->refcnt == 0)
                tcf_chain_destroy(chain);
 }
 EXPORT_SYMBOL(tcf_chain_put);
@@ -247,7 +241,7 @@ tcf_chain_filter_chain_ptr_set(struct tcf_chain *chain,
 }
 
 int tcf_block_get(struct tcf_block **p_block,
-                 struct tcf_proto __rcu **p_filter_chain)
+                 struct tcf_proto __rcu **p_filter_chain, struct Qdisc *q)
 {
        struct tcf_block *block = kzalloc(sizeof(*block), GFP_KERNEL);
        struct tcf_chain *chain;
@@ -263,6 +257,8 @@ int tcf_block_get(struct tcf_block **p_block,
                goto err_chain_create;
        }
        tcf_chain_filter_chain_ptr_set(chain, p_filter_chain);
+       block->net = qdisc_net(q);
+       block->q = q;
        *p_block = block;
        return 0;
 
@@ -279,10 +275,31 @@ void tcf_block_put(struct tcf_block *block)
        if (!block)
                return;
 
-       list_for_each_entry_safe(chain, tmp, &block->chain_list, list) {
+       /* XXX: Standalone actions are not allowed to jump to any chain, and
+        * bound actions should be all removed after flushing. However,
+        * filters are destroyed in RCU callbacks, we have to hold the chains
+        * first, otherwise we would always race with RCU callbacks on this list
+        * without proper locking.
+        */
+
+       /* Wait for existing RCU callbacks to cool down. */
+       rcu_barrier();
+
+       /* Hold a refcnt for all chains, except 0, in case they are gone. */
+       list_for_each_entry(chain, &block->chain_list, list)
+               if (chain->index)
+                       tcf_chain_hold(chain);
+
+       /* No race on the list, because no chain could be destroyed. */
+       list_for_each_entry(chain, &block->chain_list, list)
                tcf_chain_flush(chain);
-               tcf_chain_destroy(chain);
-       }
+
+       /* Wait for RCU callbacks to release the reference count. */
+       rcu_barrier();
+
+       /* At this point, all the chains should have refcnt == 1. */
+       list_for_each_entry_safe(chain, tmp, &block->chain_list, list)
+               tcf_chain_put(chain);
        kfree(block);
 }
 EXPORT_SYMBOL(tcf_block_put);
@@ -360,6 +377,7 @@ static void tcf_chain_tp_insert(struct tcf_chain *chain,
                rcu_assign_pointer(*chain->p_filter_chain, tp);
        RCU_INIT_POINTER(tp->next, tcf_chain_tp_prev(chain_info));
        rcu_assign_pointer(*chain_info->pprev, tp);
+       tcf_chain_hold(chain);
 }
 
 static void tcf_chain_tp_remove(struct tcf_chain *chain,
@@ -371,6 +389,7 @@ static void tcf_chain_tp_remove(struct tcf_chain *chain,
        if (chain->p_filter_chain && tp == chain->filter_chain)
                RCU_INIT_POINTER(*chain->p_filter_chain, next);
        RCU_INIT_POINTER(*chain_info->pprev, next);
+       tcf_chain_put(chain);
 }
 
 static struct tcf_proto *tcf_chain_tp_find(struct tcf_chain *chain,
@@ -987,29 +1006,42 @@ int tcf_exts_dump_stats(struct sk_buff *skb, struct tcf_exts *exts)
 }
 EXPORT_SYMBOL(tcf_exts_dump_stats);
 
-int tcf_exts_get_dev(struct net_device *dev, struct tcf_exts *exts,
-                    struct net_device **hw_dev)
+static int tc_exts_setup_cb_egdev_call(struct tcf_exts *exts,
+                                      enum tc_setup_type type,
+                                      void *type_data, bool err_stop)
 {
+       int ok_count = 0;
 #ifdef CONFIG_NET_CLS_ACT
        const struct tc_action *a;
+       struct net_device *dev;
        LIST_HEAD(actions);
+       int ret;
 
        if (!tcf_exts_has_actions(exts))
-               return -EINVAL;
+               return 0;
 
        tcf_exts_to_list(exts, &actions);
        list_for_each_entry(a, &actions, list) {
-               if (a->ops->get_dev) {
-                       a->ops->get_dev(a, dev_net(dev), hw_dev);
-                       break;
-               }
+               if (!a->ops->get_dev)
+                       continue;
+               dev = a->ops->get_dev(a);
+               if (!dev || !tc_can_offload(dev))
+                       continue;
+               ret = tc_setup_cb_egdev_call(dev, type, type_data, err_stop);
+               if (ret < 0)
+                       return ret;
+               ok_count += ret;
        }
-       if (*hw_dev)
-               return 0;
 #endif
-       return -EOPNOTSUPP;
+       return ok_count;
+}
+
+int tc_setup_cb_call(struct tcf_exts *exts, enum tc_setup_type type,
+                    void *type_data, bool err_stop)
+{
+       return tc_exts_setup_cb_egdev_call(exts, type, type_data, err_stop);
 }
-EXPORT_SYMBOL(tcf_exts_get_dev);
+EXPORT_SYMBOL(tc_setup_cb_call);
 
 static int __init tc_filter_init(void)
 {