When handling RTM_GETACTION (e.g. tc actions get/list), make a callback to
 blocks with hardware offload of the action to update stats from hardware.
In order to support this, track each action/block binding by allocating a
 struct tc_action_block_binding and adding it to a list on the action.

Signed-off-by: Edward Cree <ec...@solarflare.com>
---
 include/linux/netdevice.h |  1 +
 include/net/act_api.h     |  2 +-
 include/net/pkt_cls.h     | 18 ++++++++++++++
 net/sched/act_api.c       | 51 +++++++++++++++++++++++++++++++++++++++
 net/sched/cls_flower.c    |  7 ++++++
 5 files changed, 78 insertions(+), 1 deletion(-)

diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 44b47e9df94a..dee84954a1c7 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -849,6 +849,7 @@ enum tc_setup_type {
        TC_SETUP_QDISC_ETF,
        TC_SETUP_ROOT_QDISC,
        TC_SETUP_QDISC_GRED,
+       TC_SETUP_ACTION,
 };
 
 /* These structures hold the attributes of bpf state that are being passed
diff --git a/include/net/act_api.h b/include/net/act_api.h
index c61a1bf4e3de..38d1769f279b 100644
--- a/include/net/act_api.h
+++ b/include/net/act_api.h
@@ -40,6 +40,7 @@ struct tc_action {
        struct gnet_stats_queue __percpu *cpu_qstats;
        struct tc_cookie        __rcu *act_cookie;
        struct tcf_chain        __rcu *goto_chain;
+       struct list_head                hw_blocks;
 };
 #define tcf_index      common.tcfa_index
 #define tcf_refcnt     common.tcfa_refcnt
@@ -199,5 +200,4 @@ static inline void tcf_action_stats_update(struct tc_action 
*a, u64 bytes,
 #endif
 }
 
-
 #endif
diff --git a/include/net/pkt_cls.h b/include/net/pkt_cls.h
index 0e17ea8ba302..5902131c1240 100644
--- a/include/net/pkt_cls.h
+++ b/include/net/pkt_cls.h
@@ -342,6 +342,14 @@ static inline void tcf_exts_put_net(struct tcf_exts *exts)
        for (; 0; (void)(i), (void)(a), (void)(exts))
 #endif
 
+struct tc_action_block_binding {
+       struct list_head list;
+       struct tcf_block *block;
+};
+
+void tc_bind_action_blocks(struct tcf_exts *exts, struct tcf_block *block);
+void tc_unbind_action_blocks(struct tcf_exts *exts, struct tcf_block *block);
+
 static inline void
 tcf_exts_stats_update(const struct tcf_exts *exts,
                      const struct flow_stats *stats)
@@ -958,4 +966,14 @@ struct tc_root_qopt_offload {
        bool ingress;
 };
 
+enum tc_action_command {
+       TC_ACTION_STATS,
+};
+
+struct tc_action_offload {
+       enum tc_action_command command;
+       unsigned long cookie;
+       struct flow_stats_entry *stats;
+};
+
 #endif
diff --git a/net/sched/act_api.c b/net/sched/act_api.c
index 683fcc00da49..79bd63000bd6 100644
--- a/net/sched/act_api.c
+++ b/net/sched/act_api.c
@@ -427,6 +427,7 @@ int tcf_idr_create(struct tc_action_net *tn, u32 index, 
struct nlattr *est,
                        goto err3;
        }
        spin_lock_init(&p->tcfa_lock);
+       INIT_LIST_HEAD(&p->hw_blocks);
        p->tcfa_index = index;
        p->tcfa_tm.install = jiffies;
        p->tcfa_tm.lastuse = jiffies;
@@ -753,6 +754,21 @@ tcf_action_dump_old(struct sk_buff *skb, struct tc_action 
*a, int bind, int ref)
        return a->ops->dump(skb, a, bind, ref);
 }
 
+static void tcf_action_update_stats(struct tc_action *a)
+{
+       struct tc_action_block_binding *bind;
+       struct tc_action_offload offl = {};
+       struct flow_stats_entry stats = {};
+
+       offl.command = TC_ACTION_STATS;
+       offl.cookie = (unsigned long)a;
+       offl.stats = &stats;
+       ASSERT_RTNL();
+       list_for_each_entry(bind, &a->hw_blocks, list)
+               tc_setup_cb_call(bind->block, TC_SETUP_ACTION, &offl, false);
+       tcf_action_stats_update(a, stats.bytes, stats.pkts, stats.lastused, 
true);
+}
+
 int
 tcf_action_dump_1(struct sk_buff *skb, struct tc_action *a, int bind, int ref)
 {
@@ -763,6 +779,7 @@ tcf_action_dump_1(struct sk_buff *skb, struct tc_action *a, 
int bind, int ref)
 
        if (nla_put_string(skb, TCA_KIND, a->ops->kind))
                goto nla_put_failure;
+       tcf_action_update_stats(a);
        if (tcf_action_copy_stats(skb, a, 0))
                goto nla_put_failure;
 
@@ -1539,6 +1556,40 @@ static int tc_dump_action(struct sk_buff *skb, struct 
netlink_callback *cb)
        return skb->len;
 }
 
+/** Add a binding for %block to hw_blocks list of each action in %exts */
+void tc_bind_action_blocks(struct tcf_exts *exts, struct tcf_block *block)
+{
+       struct tc_action_block_binding *bind;
+       struct tc_action *act;
+       int i;
+
+       tcf_exts_for_each_action(i, act, exts) {
+               bind = kzalloc(sizeof(*bind), GFP_KERNEL);
+               if (WARN_ON_ONCE(!bind))
+                       continue; /* just skip it, stats won't update timely */
+               bind->block = block;
+               list_add_tail(&bind->list, &act->hw_blocks);
+       }
+}
+EXPORT_SYMBOL(tc_bind_action_blocks);
+
+/** Remove one instance of %block from binding list of each action in %exts */
+void tc_unbind_action_blocks(struct tcf_exts *exts, struct tcf_block *block)
+{
+       struct tc_action_block_binding *bind;
+       struct tc_action *act;
+       int i;
+
+       tcf_exts_for_each_action(i, act, exts)
+               list_for_each_entry(bind, &act->hw_blocks, list)
+                       if (bind->block == block) {
+                               list_del(&bind->list);
+                               kfree(bind);
+                               break;
+                       }
+}
+EXPORT_SYMBOL(tc_unbind_action_blocks);
+
 static int __init tc_action_init(void)
 {
        rtnl_register(PF_UNSPEC, RTM_NEWACTION, tc_ctl_action, NULL, 0);
diff --git a/net/sched/cls_flower.c b/net/sched/cls_flower.c
index 8775657fb03b..87f27cd02ba4 100644
--- a/net/sched/cls_flower.c
+++ b/net/sched/cls_flower.c
@@ -394,6 +394,8 @@ static void fl_hw_destroy_filter(struct tcf_proto *tp, 
struct cls_fl_filter *f,
        cls_flower.cookie = (unsigned long) f;
 
        tc_setup_cb_call(block, TC_SETUP_CLSFLOWER, &cls_flower, false);
+       if (f->in_hw_count)
+               tc_unbind_action_blocks(&f->exts, block);
        spin_lock(&tp->lock);
        list_del_init(&f->hw_list);
        tcf_block_offload_dec(block, &f->flags);
@@ -448,6 +450,7 @@ static int fl_hw_replace_filter(struct tcf_proto *tp,
                goto errout;
        } else if (err > 0) {
                f->in_hw_count = err;
+               tc_bind_action_blocks(&f->exts, block);
                err = 0;
                spin_lock(&tp->lock);
                tcf_block_offload_inc(block, &f->flags);
@@ -1792,10 +1795,14 @@ static int fl_reoffload(struct tcf_proto *tp, bool add, 
tc_setup_cb_t *cb,
                        goto next_flow;
                }
 
+               if (add && !f->in_hw_count)
+                       tc_bind_action_blocks(&f->exts, block);
                spin_lock(&tp->lock);
                tc_cls_offload_cnt_update(block, &f->in_hw_count, &f->flags,
                                          add);
                spin_unlock(&tp->lock);
+               if (!add && !f->in_hw_count)
+                       tc_unbind_action_blocks(&f->exts, block);
 next_flow:
                __fl_put(f);
        }

Reply via email to