diff options
Diffstat (limited to 'net/sched/act_api.c')
| -rw-r--r-- | net/sched/act_api.c | 459 | 
1 files changed, 448 insertions, 11 deletions
diff --git a/net/sched/act_api.c b/net/sched/act_api.c index 3258da3d5bed..32563cef85bf 100644 --- a/net/sched/act_api.c +++ b/net/sched/act_api.c @@ -19,8 +19,10 @@  #include <net/sock.h>  #include <net/sch_generic.h>  #include <net/pkt_cls.h> +#include <net/tc_act/tc_pedit.h>  #include <net/act_api.h>  #include <net/netlink.h> +#include <net/flow_offload.h>  #ifdef CONFIG_INET  DEFINE_STATIC_KEY_FALSE(tcf_frag_xmit_count); @@ -129,8 +131,244 @@ static void free_tcf(struct tc_action *p)  	kfree(p);  } +static void offload_action_hw_count_set(struct tc_action *act, +					u32 hw_count) +{ +	act->in_hw_count = hw_count; +} + +static void offload_action_hw_count_inc(struct tc_action *act, +					u32 hw_count) +{ +	act->in_hw_count += hw_count; +} + +static void offload_action_hw_count_dec(struct tc_action *act, +					u32 hw_count) +{ +	act->in_hw_count = act->in_hw_count > hw_count ? +			   act->in_hw_count - hw_count : 0; +} + +static unsigned int tcf_offload_act_num_actions_single(struct tc_action *act) +{ +	if (is_tcf_pedit(act)) +		return tcf_pedit_nkeys(act); +	else +		return 1; +} + +static bool tc_act_skip_hw(u32 flags) +{ +	return (flags & TCA_ACT_FLAGS_SKIP_HW) ? true : false; +} + +static bool tc_act_skip_sw(u32 flags) +{ +	return (flags & TCA_ACT_FLAGS_SKIP_SW) ? true : false; +} + +static bool tc_act_in_hw(struct tc_action *act) +{ +	return !!act->in_hw_count; +} + +/* SKIP_HW and SKIP_SW are mutually exclusive flags. */ +static bool tc_act_flags_valid(u32 flags) +{ +	flags &= TCA_ACT_FLAGS_SKIP_HW | TCA_ACT_FLAGS_SKIP_SW; + +	return flags ^ (TCA_ACT_FLAGS_SKIP_HW | TCA_ACT_FLAGS_SKIP_SW); +} + +static int offload_action_init(struct flow_offload_action *fl_action, +			       struct tc_action *act, +			       enum offload_act_command  cmd, +			       struct netlink_ext_ack *extack) +{ +	int err; + +	fl_action->extack = extack; +	fl_action->command = cmd; +	fl_action->index = act->tcfa_index; + +	if (act->ops->offload_act_setup) { +		spin_lock_bh(&act->tcfa_lock); +		err = act->ops->offload_act_setup(act, fl_action, NULL, +						  false); +		spin_unlock_bh(&act->tcfa_lock); +		return err; +	} + +	return -EOPNOTSUPP; +} + +static int tcf_action_offload_cmd_ex(struct flow_offload_action *fl_act, +				     u32 *hw_count) +{ +	int err; + +	err = flow_indr_dev_setup_offload(NULL, NULL, TC_SETUP_ACT, +					  fl_act, NULL, NULL); +	if (err < 0) +		return err; + +	if (hw_count) +		*hw_count = err; + +	return 0; +} + +static int tcf_action_offload_cmd_cb_ex(struct flow_offload_action *fl_act, +					u32 *hw_count, +					flow_indr_block_bind_cb_t *cb, +					void *cb_priv) +{ +	int err; + +	err = cb(NULL, NULL, cb_priv, TC_SETUP_ACT, NULL, fl_act, NULL); +	if (err < 0) +		return err; + +	if (hw_count) +		*hw_count = 1; + +	return 0; +} + +static int tcf_action_offload_cmd(struct flow_offload_action *fl_act, +				  u32 *hw_count, +				  flow_indr_block_bind_cb_t *cb, +				  void *cb_priv) +{ +	return cb ? tcf_action_offload_cmd_cb_ex(fl_act, hw_count, +						 cb, cb_priv) : +		    tcf_action_offload_cmd_ex(fl_act, hw_count); +} + +static int tcf_action_offload_add_ex(struct tc_action *action, +				     struct netlink_ext_ack *extack, +				     flow_indr_block_bind_cb_t *cb, +				     void *cb_priv) +{ +	bool skip_sw = tc_act_skip_sw(action->tcfa_flags); +	struct tc_action *actions[TCA_ACT_MAX_PRIO] = { +		[0] = action, +	}; +	struct flow_offload_action *fl_action; +	u32 in_hw_count = 0; +	int num, err = 0; + +	if (tc_act_skip_hw(action->tcfa_flags)) +		return 0; + +	num = tcf_offload_act_num_actions_single(action); +	fl_action = offload_action_alloc(num); +	if (!fl_action) +		return -ENOMEM; + +	err = offload_action_init(fl_action, action, FLOW_ACT_REPLACE, extack); +	if (err) +		goto fl_err; + +	err = tc_setup_action(&fl_action->action, actions); +	if (err) { +		NL_SET_ERR_MSG_MOD(extack, +				   "Failed to setup tc actions for offload\n"); +		goto fl_err; +	} + +	err = tcf_action_offload_cmd(fl_action, &in_hw_count, cb, cb_priv); +	if (!err) +		cb ? offload_action_hw_count_inc(action, in_hw_count) : +		     offload_action_hw_count_set(action, in_hw_count); + +	if (skip_sw && !tc_act_in_hw(action)) +		err = -EINVAL; + +	tc_cleanup_offload_action(&fl_action->action); + +fl_err: +	kfree(fl_action); + +	return err; +} + +/* offload the tc action after it is inserted */ +static int tcf_action_offload_add(struct tc_action *action, +				  struct netlink_ext_ack *extack) +{ +	return tcf_action_offload_add_ex(action, extack, NULL, NULL); +} + +int tcf_action_update_hw_stats(struct tc_action *action) +{ +	struct flow_offload_action fl_act = {}; +	int err; + +	if (!tc_act_in_hw(action)) +		return -EOPNOTSUPP; + +	err = offload_action_init(&fl_act, action, FLOW_ACT_STATS, NULL); +	if (err) +		return err; + +	err = tcf_action_offload_cmd(&fl_act, NULL, NULL, NULL); +	if (!err) { +		preempt_disable(); +		tcf_action_stats_update(action, fl_act.stats.bytes, +					fl_act.stats.pkts, +					fl_act.stats.drops, +					fl_act.stats.lastused, +					true); +		preempt_enable(); +		action->used_hw_stats = fl_act.stats.used_hw_stats; +		action->used_hw_stats_valid = true; +	} else { +		return -EOPNOTSUPP; +	} + +	return 0; +} +EXPORT_SYMBOL(tcf_action_update_hw_stats); + +static int tcf_action_offload_del_ex(struct tc_action *action, +				     flow_indr_block_bind_cb_t *cb, +				     void *cb_priv) +{ +	struct flow_offload_action fl_act = {}; +	u32 in_hw_count = 0; +	int err = 0; + +	if (!tc_act_in_hw(action)) +		return 0; + +	err = offload_action_init(&fl_act, action, FLOW_ACT_DESTROY, NULL); +	if (err) +		return err; + +	err = tcf_action_offload_cmd(&fl_act, &in_hw_count, cb, cb_priv); +	if (err < 0) +		return err; + +	if (!cb && action->in_hw_count != in_hw_count) +		return -EINVAL; + +	/* do not need to update hw state when deleting action */ +	if (cb && in_hw_count) +		offload_action_hw_count_dec(action, in_hw_count); + +	return 0; +} + +static int tcf_action_offload_del(struct tc_action *action) +{ +	return tcf_action_offload_del_ex(action, NULL, NULL); +} +  static void tcf_action_cleanup(struct tc_action *p)  { +	tcf_action_offload_del(p);  	if (p->ops->cleanup)  		p->ops->cleanup(p); @@ -497,7 +735,7 @@ int tcf_idr_create(struct tc_action_net *tn, u32 index, struct nlattr *est,  	p->tcfa_tm.install = jiffies;  	p->tcfa_tm.lastuse = jiffies;  	p->tcfa_tm.firstuse = 0; -	p->tcfa_flags = flags & TCA_ACT_FLAGS_USER_MASK; +	p->tcfa_flags = flags;  	if (est) {  		err = gen_new_estimator(&p->tcfa_bstats, p->cpu_bstats,  					&p->tcfa_rate_est, @@ -622,6 +860,59 @@ EXPORT_SYMBOL(tcf_idrinfo_destroy);  static LIST_HEAD(act_base);  static DEFINE_RWLOCK(act_mod_lock); +/* since act ops id is stored in pernet subsystem list, + * then there is no way to walk through only all the action + * subsystem, so we keep tc action pernet ops id for + * reoffload to walk through. + */ +static LIST_HEAD(act_pernet_id_list); +static DEFINE_MUTEX(act_id_mutex); +struct tc_act_pernet_id { +	struct list_head list; +	unsigned int id; +}; + +static int tcf_pernet_add_id_list(unsigned int id) +{ +	struct tc_act_pernet_id *id_ptr; +	int ret = 0; + +	mutex_lock(&act_id_mutex); +	list_for_each_entry(id_ptr, &act_pernet_id_list, list) { +		if (id_ptr->id == id) { +			ret = -EEXIST; +			goto err_out; +		} +	} + +	id_ptr = kzalloc(sizeof(*id_ptr), GFP_KERNEL); +	if (!id_ptr) { +		ret = -ENOMEM; +		goto err_out; +	} +	id_ptr->id = id; + +	list_add_tail(&id_ptr->list, &act_pernet_id_list); + +err_out: +	mutex_unlock(&act_id_mutex); +	return ret; +} + +static void tcf_pernet_del_id_list(unsigned int id) +{ +	struct tc_act_pernet_id *id_ptr; + +	mutex_lock(&act_id_mutex); +	list_for_each_entry(id_ptr, &act_pernet_id_list, list) { +		if (id_ptr->id == id) { +			list_del(&id_ptr->list); +			kfree(id_ptr); +			break; +		} +	} +	mutex_unlock(&act_id_mutex); +}  int tcf_register_action(struct tc_action_ops *act,  			struct pernet_operations *ops) @@ -640,18 +931,31 @@ int tcf_register_action(struct tc_action_ops *act,  	if (ret)  		return ret; +	if (ops->id) { +		ret = tcf_pernet_add_id_list(*ops->id); +		if (ret) +			goto err_id; +	} +  	write_lock(&act_mod_lock);  	list_for_each_entry(a, &act_base, head) {  		if (act->id == a->id || (strcmp(act->kind, a->kind) == 0)) { -			write_unlock(&act_mod_lock); -			unregister_pernet_subsys(ops); -			return -EEXIST; +			ret = -EEXIST; +			goto err_out;  		}  	}  	list_add_tail(&act->head, &act_base);  	write_unlock(&act_mod_lock);  	return 0; + +err_out: +	write_unlock(&act_mod_lock); +	if (ops->id) +		tcf_pernet_del_id_list(*ops->id); +err_id: +	unregister_pernet_subsys(ops); +	return ret;  }  EXPORT_SYMBOL(tcf_register_action); @@ -670,8 +974,11 @@ int tcf_unregister_action(struct tc_action_ops *act,  		}  	}  	write_unlock(&act_mod_lock); -	if (!err) +	if (!err) {  		unregister_pernet_subsys(ops); +		if (ops->id) +			tcf_pernet_del_id_list(*ops->id); +	}  	return err;  }  EXPORT_SYMBOL(tcf_unregister_action); @@ -735,6 +1042,9 @@ restart_act_graph:  			jmp_prgcnt -= 1;  			continue;  		} + +		if (tc_act_skip_sw(a->tcfa_flags)) +			continue;  repeat:  		ret = a->ops->act(skb, a, res);  		if (ret == TC_ACT_REPEAT) @@ -821,6 +1131,7 @@ tcf_action_dump_1(struct sk_buff *skb, struct tc_action *a, int bind, int ref)  	int err = -EINVAL;  	unsigned char *b = skb_tail_pointer(skb);  	struct nlattr *nest; +	u32 flags;  	if (tcf_action_dump_terse(skb, a, false))  		goto nla_put_failure; @@ -835,9 +1146,13 @@ tcf_action_dump_1(struct sk_buff *skb, struct tc_action *a, int bind, int ref)  			       a->used_hw_stats, TCA_ACT_HW_STATS_ANY))  		goto nla_put_failure; -	if (a->tcfa_flags && +	flags = a->tcfa_flags & TCA_ACT_FLAGS_USER_MASK; +	if (flags &&  	    nla_put_bitfield32(skb, TCA_ACT_FLAGS, -			       a->tcfa_flags, a->tcfa_flags)) +			       flags, flags)) +		goto nla_put_failure; + +	if (nla_put_u32(skb, TCA_ACT_IN_HW_COUNT, a->in_hw_count))  		goto nla_put_failure;  	nest = nla_nest_start_noflag(skb, TCA_OPTIONS); @@ -919,7 +1234,9 @@ static const struct nla_policy tcf_action_policy[TCA_ACT_MAX + 1] = {  	[TCA_ACT_COOKIE]	= { .type = NLA_BINARY,  				    .len = TC_COOKIE_MAX_SIZE },  	[TCA_ACT_OPTIONS]	= { .type = NLA_NESTED }, -	[TCA_ACT_FLAGS]		= NLA_POLICY_BITFIELD32(TCA_ACT_FLAGS_NO_PERCPU_STATS), +	[TCA_ACT_FLAGS]		= NLA_POLICY_BITFIELD32(TCA_ACT_FLAGS_NO_PERCPU_STATS | +							TCA_ACT_FLAGS_SKIP_HW | +							TCA_ACT_FLAGS_SKIP_SW),  	[TCA_ACT_HW_STATS]	= NLA_POLICY_BITFIELD32(TCA_ACT_HW_STATS_ANY),  }; @@ -1032,8 +1349,13 @@ struct tc_action *tcf_action_init_1(struct net *net, struct tcf_proto *tp,  			}  		}  		hw_stats = tcf_action_hw_stats_get(tb[TCA_ACT_HW_STATS]); -		if (tb[TCA_ACT_FLAGS]) +		if (tb[TCA_ACT_FLAGS]) {  			userflags = nla_get_bitfield32(tb[TCA_ACT_FLAGS]); +			if (!tc_act_flags_valid(userflags.value)) { +				err = -EINVAL; +				goto err_out; +			} +		}  		err = a_o->init(net, tb[TCA_ACT_OPTIONS], est, &a, tp,  				userflags.value | flags, extack); @@ -1061,11 +1383,17 @@ err_out:  	return ERR_PTR(err);  } +static bool tc_act_bind(u32 flags) +{ +	return !!(flags & TCA_ACT_FLAGS_BIND); +} +  /* Returns numbers of initialized actions or negative error. */  int tcf_action_init(struct net *net, struct tcf_proto *tp, struct nlattr *nla,  		    struct nlattr *est, struct tc_action *actions[], -		    int init_res[], size_t *attr_size, u32 flags, +		    int init_res[], size_t *attr_size, +		    u32 flags, u32 fl_flags,  		    struct netlink_ext_ack *extack)  {  	struct tc_action_ops *ops[TCA_ACT_MAX_PRIO] = {}; @@ -1103,6 +1431,22 @@ int tcf_action_init(struct net *net, struct tcf_proto *tp, struct nlattr *nla,  		sz += tcf_action_fill_size(act);  		/* Start from index 0 */  		actions[i - 1] = act; +		if (tc_act_bind(flags)) { +			bool skip_sw = tc_skip_sw(fl_flags); +			bool skip_hw = tc_skip_hw(fl_flags); + +			if (tc_act_bind(act->tcfa_flags)) +				continue; +			if (skip_sw != tc_act_skip_sw(act->tcfa_flags) || +			    skip_hw != tc_act_skip_hw(act->tcfa_flags)) { +				err = -EINVAL; +				goto err; +			} +		} else { +			err = tcf_action_offload_add(act, extack); +			if (tc_act_skip_sw(act->tcfa_flags) && err) +				goto err; +		}  	}  	/* We have to commit them all together, because if any error happened in @@ -1154,6 +1498,9 @@ int tcf_action_copy_stats(struct sk_buff *skb, struct tc_action *p,  	if (p == NULL)  		goto errout; +	/* update hw stats for this action */ +	tcf_action_update_hw_stats(p); +  	/* compat_mode being true specifies a call that is supposed  	 * to add additional backward compatibility statistic TLVs.  	 */ @@ -1396,6 +1743,96 @@ static int tcf_action_delete(struct net *net, struct tc_action *actions[])  }  static int +tcf_reoffload_del_notify(struct net *net, struct tc_action *action) +{ +	size_t attr_size = tcf_action_fill_size(action); +	struct tc_action *actions[TCA_ACT_MAX_PRIO] = { +		[0] = action, +	}; +	const struct tc_action_ops *ops = action->ops; +	struct sk_buff *skb; +	int ret; + +	skb = alloc_skb(attr_size <= NLMSG_GOODSIZE ? NLMSG_GOODSIZE : attr_size, +			GFP_KERNEL); +	if (!skb) +		return -ENOBUFS; + +	if (tca_get_fill(skb, actions, 0, 0, 0, RTM_DELACTION, 0, 1) <= 0) { +		kfree_skb(skb); +		return -EINVAL; +	} + +	ret = tcf_idr_release_unsafe(action); +	if (ret == ACT_P_DELETED) { +		module_put(ops->owner); +		ret = rtnetlink_send(skb, net, 0, RTNLGRP_TC, 0); +	} else { +		kfree_skb(skb); +	} + +	return ret; +} + +int tcf_action_reoffload_cb(flow_indr_block_bind_cb_t *cb, +			    void *cb_priv, bool add) +{ +	struct tc_act_pernet_id *id_ptr; +	struct tcf_idrinfo *idrinfo; +	struct tc_action_net *tn; +	struct tc_action *p; +	unsigned int act_id; +	unsigned long tmp; +	unsigned long id; +	struct idr *idr; +	struct net *net; +	int ret; + +	if (!cb) +		return -EINVAL; + +	down_read(&net_rwsem); +	mutex_lock(&act_id_mutex); + +	for_each_net(net) { +		list_for_each_entry(id_ptr, &act_pernet_id_list, list) { +			act_id = id_ptr->id; +			tn = net_generic(net, act_id); +			if (!tn) +				continue; +			idrinfo = tn->idrinfo; +			if (!idrinfo) +				continue; + +			mutex_lock(&idrinfo->lock); +			idr = &idrinfo->action_idr; +			idr_for_each_entry_ul(idr, p, tmp, id) { +				if (IS_ERR(p) || tc_act_bind(p->tcfa_flags)) +					continue; +				if (add) { +					tcf_action_offload_add_ex(p, NULL, cb, +								  cb_priv); +					continue; +				} + +				/* cb unregister to update hw count */ +				ret = tcf_action_offload_del_ex(p, cb, cb_priv); +				if (ret < 0) +					continue; +				if (tc_act_skip_sw(p->tcfa_flags) && +				    !tc_act_in_hw(p)) +					tcf_reoffload_del_notify(net, p); +			} +			mutex_unlock(&idrinfo->lock); +		} +	} +	mutex_unlock(&act_id_mutex); +	up_read(&net_rwsem); + +	return 0; +} + +static int  tcf_del_notify(struct net *net, struct nlmsghdr *n, struct tc_action *actions[],  	       u32 portid, size_t attr_size, struct netlink_ext_ack *extack)  { @@ -1508,7 +1945,7 @@ static int tcf_action_add(struct net *net, struct nlattr *nla,  	for (loop = 0; loop < 10; loop++) {  		ret = tcf_action_init(net, NULL, nla, NULL, actions, init_res, -				      &attr_size, flags, extack); +				      &attr_size, flags, 0, extack);  		if (ret != -EAGAIN)  			break;  	}  |