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); }