Merge branch 'net-allow-user-specify-TC-action-HW-stats-type'

Jiri Pirko says:

====================
net: allow user specify TC action HW stats type

Currently, when user adds a TC action and the action gets offloaded,
the user expects the HW stats to be counted and included in stats dump.
However, since drivers may implement different types of counting, there
is no way to specify which one the user is interested in.

For example for mlx5, only delayed counters are available as the driver
periodically polls for updated stats.

In case of mlxsw, the counters are queried on dump time. However, the
HW resources for this type of counters is quite limited (couple of
thousands). This limits the amount of supported offloaded filters
significantly. Without counter assigned, the HW is capable to carry
millions of those.

On top of that, mlxsw HW is able to support delayed counters as well in
greater numbers. That is going to be added in a follow-up patch.

This patchset allows user to specify one of the following types of HW
stats for added action:
immediate - queried during dump time
delayed - polled from HW periodically or sent by HW in async manner
disabled - no stats needed

Note that if "hw_stats" option is not passed, user does not care about
the type, just expects any type of stats.

Examples:
$ tc filter add dev enp0s16np28 ingress proto ip handle 1 pref 1 flower skip_sw dst_ip 192.168.1.1 action drop hw_stats disabled
$ tc -s filter show dev enp0s16np28 ingress
filter protocol ip pref 1 flower chain 0
filter protocol ip pref 1 flower chain 0 handle 0x1
  eth_type ipv4
  dst_ip 192.168.1.1
  skip_sw
  in_hw in_hw_count 2
        action order 1: gact action drop
         random type none pass val 0
         index 1 ref 1 bind 1 installed 7 sec used 2 sec
        Action statistics:
        Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
        backlog 0b 0p requeues 0
        hw_stats disabled

$ tc filter add dev enp0s16np28 ingress proto ip handle 1 pref 1 flower skip_sw dst_ip 192.168.1.1 action drop hw_stats immediate
$ tc -s filter show dev enp0s16np28 ingress
filter protocol ip pref 1 flower chain 0
filter protocol ip pref 1 flower chain 0 handle 0x1
  eth_type ipv4
  dst_ip 192.168.1.1
  skip_sw
  in_hw in_hw_count 2
        action order 1: gact action drop
         random type none pass val 0
         index 1 ref 1 bind 1 installed 11 sec used 4 sec
        Action statistics:
        Sent 102 bytes 1 pkt (dropped 1, overlimits 0 requeues 0)
        Sent software 0 bytes 0 pkt
        Sent hardware 102 bytes 1 pkt
        backlog 0b 0p requeues 0
        hw_stats immediate
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
David S. Miller 2020-03-08 21:07:48 -07:00
commit 9d2e4e1648
19 changed files with 230 additions and 26 deletions

View File

@ -279,7 +279,8 @@ bnxt_tc_parse_pedit(struct bnxt *bp, struct bnxt_tc_actions *actions,
static int bnxt_tc_parse_actions(struct bnxt *bp, static int bnxt_tc_parse_actions(struct bnxt *bp,
struct bnxt_tc_actions *actions, struct bnxt_tc_actions *actions,
struct flow_action *flow_action) struct flow_action *flow_action,
struct netlink_ext_ack *extack)
{ {
/* Used to store the L2 rewrite mask for dmac (6 bytes) followed by /* Used to store the L2 rewrite mask for dmac (6 bytes) followed by
* smac (6 bytes) if rewrite of both is specified, otherwise either * smac (6 bytes) if rewrite of both is specified, otherwise either
@ -299,6 +300,9 @@ static int bnxt_tc_parse_actions(struct bnxt *bp,
return -EINVAL; return -EINVAL;
} }
if (!flow_action_basic_hw_stats_types_check(flow_action, extack))
return -EOPNOTSUPP;
flow_action_for_each(i, act, flow_action) { flow_action_for_each(i, act, flow_action) {
switch (act->id) { switch (act->id) {
case FLOW_ACTION_DROP: case FLOW_ACTION_DROP:
@ -491,7 +495,8 @@ static int bnxt_tc_parse_flow(struct bnxt *bp,
flow->tun_mask.tp_src = match.mask->src; flow->tun_mask.tp_src = match.mask->src;
} }
return bnxt_tc_parse_actions(bp, &flow->actions, &rule->action); return bnxt_tc_parse_actions(bp, &flow->actions, &rule->action,
tc_flow_cmd->common.extack);
} }
static int bnxt_hwrm_cfa_flow_free(struct bnxt *bp, static int bnxt_hwrm_cfa_flow_free(struct bnxt *bp,

View File

@ -544,7 +544,8 @@ static bool valid_pedit_action(struct net_device *dev,
} }
int cxgb4_validate_flow_actions(struct net_device *dev, int cxgb4_validate_flow_actions(struct net_device *dev,
struct flow_action *actions) struct flow_action *actions,
struct netlink_ext_ack *extack)
{ {
struct flow_action_entry *act; struct flow_action_entry *act;
bool act_redir = false; bool act_redir = false;
@ -552,6 +553,9 @@ int cxgb4_validate_flow_actions(struct net_device *dev,
bool act_vlan = false; bool act_vlan = false;
int i; int i;
if (!flow_action_basic_hw_stats_types_check(actions, extack))
return -EOPNOTSUPP;
flow_action_for_each(i, act, actions) { flow_action_for_each(i, act, actions) {
switch (act->id) { switch (act->id) {
case FLOW_ACTION_ACCEPT: case FLOW_ACTION_ACCEPT:
@ -642,7 +646,7 @@ int cxgb4_tc_flower_replace(struct net_device *dev,
struct filter_ctx ctx; struct filter_ctx ctx;
int fidx, ret; int fidx, ret;
if (cxgb4_validate_flow_actions(dev, &rule->action)) if (cxgb4_validate_flow_actions(dev, &rule->action, extack))
return -EOPNOTSUPP; return -EOPNOTSUPP;
if (cxgb4_validate_flow_match(dev, cls)) if (cxgb4_validate_flow_match(dev, cls))

View File

@ -112,7 +112,8 @@ void cxgb4_process_flow_actions(struct net_device *in,
struct flow_action *actions, struct flow_action *actions,
struct ch_filter_specification *fs); struct ch_filter_specification *fs);
int cxgb4_validate_flow_actions(struct net_device *dev, int cxgb4_validate_flow_actions(struct net_device *dev,
struct flow_action *actions); struct flow_action *actions,
struct netlink_ext_ack *extack);
int cxgb4_tc_flower_replace(struct net_device *dev, int cxgb4_tc_flower_replace(struct net_device *dev,
struct flow_cls_offload *cls); struct flow_cls_offload *cls);

View File

@ -286,7 +286,8 @@ int cxgb4_tc_matchall_replace(struct net_device *dev,
} }
ret = cxgb4_validate_flow_actions(dev, ret = cxgb4_validate_flow_actions(dev,
&cls_matchall->rule->action); &cls_matchall->rule->action,
extack);
if (ret) if (ret)
return ret; return ret;

View File

@ -1082,6 +1082,9 @@ static int mvpp2_port_c2_tcam_rule_add(struct mvpp2_port *port,
u8 qh, ql, pmap; u8 qh, ql, pmap;
int index, ctx; int index, ctx;
if (!flow_action_basic_hw_stats_types_check(&rule->flow->action, NULL))
return -EOPNOTSUPP;
memset(&c2, 0, sizeof(c2)); memset(&c2, 0, sizeof(c2));
index = mvpp2_cls_c2_port_flow_index(port, rule->loc); index = mvpp2_cls_c2_port_flow_index(port, rule->loc);
@ -1305,6 +1308,9 @@ static int mvpp2_cls_rfs_parse_rule(struct mvpp2_rfs_rule *rule)
struct flow_rule *flow = rule->flow; struct flow_rule *flow = rule->flow;
struct flow_action_entry *act; struct flow_action_entry *act;
if (!flow_action_basic_hw_stats_types_check(&rule->flow->action, NULL))
return -EOPNOTSUPP;
act = &flow->action.entries[0]; act = &flow->action.entries[0];
if (act->id != FLOW_ACTION_QUEUE && act->id != FLOW_ACTION_DROP) if (act->id != FLOW_ACTION_QUEUE && act->id != FLOW_ACTION_DROP)
return -EOPNOTSUPP; return -EOPNOTSUPP;

View File

@ -2878,6 +2878,10 @@ static int parse_tc_nic_actions(struct mlx5e_priv *priv,
if (!flow_action_has_entries(flow_action)) if (!flow_action_has_entries(flow_action))
return -EINVAL; return -EINVAL;
if (!flow_action_hw_stats_types_check(flow_action, extack,
FLOW_ACTION_HW_STATS_TYPE_DELAYED))
return -EOPNOTSUPP;
attr->flow_tag = MLX5_FS_DEFAULT_FLOW_TAG; attr->flow_tag = MLX5_FS_DEFAULT_FLOW_TAG;
flow_action_for_each(i, act, flow_action) { flow_action_for_each(i, act, flow_action) {
@ -3330,6 +3334,10 @@ static int parse_tc_fdb_actions(struct mlx5e_priv *priv,
if (!flow_action_has_entries(flow_action)) if (!flow_action_has_entries(flow_action))
return -EINVAL; return -EINVAL;
if (!flow_action_hw_stats_types_check(flow_action, extack,
FLOW_ACTION_HW_STATS_TYPE_DELAYED))
return -EOPNOTSUPP;
flow_action_for_each(i, act, flow_action) { flow_action_for_each(i, act, flow_action) {
switch (act->id) { switch (act->id) {
case FLOW_ACTION_DROP: case FLOW_ACTION_DROP:
@ -4148,6 +4156,9 @@ static int scan_tc_matchall_fdb_actions(struct mlx5e_priv *priv,
return -EOPNOTSUPP; return -EOPNOTSUPP;
} }
if (!flow_action_basic_hw_stats_types_check(flow_action, extack))
return -EOPNOTSUPP;
flow_action_for_each(i, act, flow_action) { flow_action_for_each(i, act, flow_action) {
switch (act->id) { switch (act->id) {
case FLOW_ACTION_POLICE: case FLOW_ACTION_POLICE:

View File

@ -641,7 +641,8 @@ struct mlxsw_sp_acl_rule_info {
struct mlxsw_afa_block *act_block; struct mlxsw_afa_block *act_block;
u8 action_created:1, u8 action_created:1,
ingress_bind_blocker:1, ingress_bind_blocker:1,
egress_bind_blocker:1; egress_bind_blocker:1,
counter_valid:1;
unsigned int counter_index; unsigned int counter_index;
}; };

View File

@ -642,8 +642,14 @@ int mlxsw_sp_acl_rulei_act_count(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_rule_info *rulei, struct mlxsw_sp_acl_rule_info *rulei,
struct netlink_ext_ack *extack) struct netlink_ext_ack *extack)
{ {
return mlxsw_afa_block_append_counter(rulei->act_block, int err;
&rulei->counter_index, extack);
err = mlxsw_afa_block_append_counter(rulei->act_block,
&rulei->counter_index, extack);
if (err)
return err;
rulei->counter_valid = true;
return 0;
} }
int mlxsw_sp_acl_rulei_act_fid_set(struct mlxsw_sp *mlxsw_sp, int mlxsw_sp_acl_rulei_act_fid_set(struct mlxsw_sp *mlxsw_sp,
@ -857,16 +863,18 @@ int mlxsw_sp_acl_rule_get_stats(struct mlxsw_sp *mlxsw_sp,
{ {
struct mlxsw_sp_acl_rule_info *rulei; struct mlxsw_sp_acl_rule_info *rulei;
u64 current_packets; u64 current_packets = 0;
u64 current_bytes; u64 current_bytes = 0;
int err; int err;
rulei = mlxsw_sp_acl_rule_rulei(rule); rulei = mlxsw_sp_acl_rule_rulei(rule);
err = mlxsw_sp_flow_counter_get(mlxsw_sp, rulei->counter_index, if (rulei->counter_valid) {
&current_packets, &current_bytes); err = mlxsw_sp_flow_counter_get(mlxsw_sp, rulei->counter_index,
if (err) &current_packets,
return err; &current_bytes);
if (err)
return err;
}
*packets = current_packets - rule->last_packets; *packets = current_packets - rule->last_packets;
*bytes = current_bytes - rule->last_bytes; *bytes = current_bytes - rule->last_bytes;
*last_use = rule->last_used; *last_use = rule->last_used;

View File

@ -26,11 +26,20 @@ static int mlxsw_sp_flower_parse_actions(struct mlxsw_sp *mlxsw_sp,
if (!flow_action_has_entries(flow_action)) if (!flow_action_has_entries(flow_action))
return 0; return 0;
if (!flow_action_mixed_hw_stats_types_check(flow_action, extack))
return -EOPNOTSUPP;
/* Count action is inserted first */ act = flow_action_first_entry_get(flow_action);
err = mlxsw_sp_acl_rulei_act_count(mlxsw_sp, rulei, extack); if (act->hw_stats_type == FLOW_ACTION_HW_STATS_TYPE_ANY ||
if (err) act->hw_stats_type == FLOW_ACTION_HW_STATS_TYPE_IMMEDIATE) {
return err; /* Count action is inserted first */
err = mlxsw_sp_acl_rulei_act_count(mlxsw_sp, rulei, extack);
if (err)
return err;
} else if (act->hw_stats_type != FLOW_ACTION_HW_STATS_TYPE_DISABLED) {
NL_SET_ERR_MSG_MOD(extack, "Unsupported action HW stats type");
return -EOPNOTSUPP;
}
flow_action_for_each(i, act, flow_action) { flow_action_for_each(i, act, flow_action) {
switch (act->id) { switch (act->id) {

View File

@ -14,7 +14,11 @@ static int ocelot_flower_parse_action(struct flow_cls_offload *f,
const struct flow_action_entry *a; const struct flow_action_entry *a;
int i; int i;
if (f->rule->action.num_entries != 1) if (!flow_offload_has_one_action(&f->rule->action))
return -EOPNOTSUPP;
if (!flow_action_basic_hw_stats_types_check(&f->rule->action,
f->common.extack))
return -EOPNOTSUPP; return -EOPNOTSUPP;
flow_action_for_each(i, a, &f->rule->action) { flow_action_for_each(i, a, &f->rule->action) {

View File

@ -1207,6 +1207,10 @@ int nfp_flower_compile_action(struct nfp_app *app,
bool pkt_host = false; bool pkt_host = false;
u32 csum_updated = 0; u32 csum_updated = 0;
if (!flow_action_basic_hw_stats_types_check(&flow->rule->action,
extack))
return -EOPNOTSUPP;
memset(nfp_flow->action_data, 0, NFP_FL_MAX_A_SIZ); memset(nfp_flow->action_data, 0, NFP_FL_MAX_A_SIZ);
nfp_flow->meta.act_len = 0; nfp_flow->meta.act_len = 0;
tun_type = NFP_FL_TUNNEL_NONE; tun_type = NFP_FL_TUNNEL_NONE;

View File

@ -1746,7 +1746,8 @@ int qede_get_arfs_filter_count(struct qede_dev *edev)
} }
static int qede_parse_actions(struct qede_dev *edev, static int qede_parse_actions(struct qede_dev *edev,
struct flow_action *flow_action) struct flow_action *flow_action,
struct netlink_ext_ack *extack)
{ {
const struct flow_action_entry *act; const struct flow_action_entry *act;
int i; int i;
@ -1756,6 +1757,9 @@ static int qede_parse_actions(struct qede_dev *edev,
return -EINVAL; return -EINVAL;
} }
if (!flow_action_basic_hw_stats_types_check(flow_action, extack))
return -EOPNOTSUPP;
flow_action_for_each(i, act, flow_action) { flow_action_for_each(i, act, flow_action) {
switch (act->id) { switch (act->id) {
case FLOW_ACTION_DROP: case FLOW_ACTION_DROP:
@ -1970,7 +1974,7 @@ int qede_add_tc_flower_fltr(struct qede_dev *edev, __be16 proto,
} }
/* parse tc actions and get the vf_id */ /* parse tc actions and get the vf_id */
if (qede_parse_actions(edev, &f->rule->action)) if (qede_parse_actions(edev, &f->rule->action, f->common.extack))
goto unlock; goto unlock;
if (qede_flow_find_fltr(edev, &t)) { if (qede_flow_find_fltr(edev, &t)) {
@ -2038,7 +2042,7 @@ static int qede_flow_spec_validate(struct qede_dev *edev,
return -EINVAL; return -EINVAL;
} }
if (qede_parse_actions(edev, flow_action)) if (qede_parse_actions(edev, flow_action, NULL))
return -EINVAL; return -EINVAL;
return 0; return 0;

View File

@ -367,7 +367,8 @@ static int tc_setup_cbs(struct stmmac_priv *priv,
static int tc_parse_flow_actions(struct stmmac_priv *priv, static int tc_parse_flow_actions(struct stmmac_priv *priv,
struct flow_action *action, struct flow_action *action,
struct stmmac_flow_entry *entry) struct stmmac_flow_entry *entry,
struct netlink_ext_ack *extack)
{ {
struct flow_action_entry *act; struct flow_action_entry *act;
int i; int i;
@ -375,6 +376,9 @@ static int tc_parse_flow_actions(struct stmmac_priv *priv,
if (!flow_action_has_entries(action)) if (!flow_action_has_entries(action))
return -EINVAL; return -EINVAL;
if (!flow_action_basic_hw_stats_types_check(action, extack))
return -EOPNOTSUPP;
flow_action_for_each(i, act, action) { flow_action_for_each(i, act, action) {
switch (act->id) { switch (act->id) {
case FLOW_ACTION_DROP: case FLOW_ACTION_DROP:
@ -530,7 +534,8 @@ static int tc_add_flow(struct stmmac_priv *priv,
return -ENOENT; return -ENOENT;
} }
ret = tc_parse_flow_actions(priv, &rule->action, entry); ret = tc_parse_flow_actions(priv, &rule->action, entry,
cls->common.extack);
if (ret) if (ret)
return ret; return ret;

View File

@ -41,6 +41,7 @@ struct tc_action {
struct tc_cookie __rcu *act_cookie; struct tc_cookie __rcu *act_cookie;
struct tcf_chain __rcu *goto_chain; struct tcf_chain __rcu *goto_chain;
u32 tcfa_flags; u32 tcfa_flags;
u8 hw_stats_type;
}; };
#define tcf_index common.tcfa_index #define tcf_index common.tcfa_index
#define tcf_refcnt common.tcfa_refcnt #define tcf_refcnt common.tcfa_refcnt
@ -52,6 +53,9 @@ struct tc_action {
#define tcf_rate_est common.tcfa_rate_est #define tcf_rate_est common.tcfa_rate_est
#define tcf_lock common.tcfa_lock #define tcf_lock common.tcfa_lock
#define TCA_ACT_HW_STATS_TYPE_ANY (TCA_ACT_HW_STATS_TYPE_IMMEDIATE | \
TCA_ACT_HW_STATS_TYPE_DELAYED)
/* Update lastuse only if needed, to avoid dirtying a cache line. /* Update lastuse only if needed, to avoid dirtying a cache line.
* We use a temp variable to avoid fetching jiffies twice. * We use a temp variable to avoid fetching jiffies twice.
*/ */

View File

@ -3,6 +3,7 @@
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/list.h> #include <linux/list.h>
#include <linux/netlink.h>
#include <net/flow_dissector.h> #include <net/flow_dissector.h>
#include <linux/rhashtable.h> #include <linux/rhashtable.h>
@ -154,6 +155,12 @@ enum flow_action_mangle_base {
FLOW_ACT_MANGLE_HDR_TYPE_UDP, FLOW_ACT_MANGLE_HDR_TYPE_UDP,
}; };
#define FLOW_ACTION_HW_STATS_TYPE_IMMEDIATE BIT(0)
#define FLOW_ACTION_HW_STATS_TYPE_DELAYED BIT(1)
#define FLOW_ACTION_HW_STATS_TYPE_ANY (FLOW_ACTION_HW_STATS_TYPE_IMMEDIATE | \
FLOW_ACTION_HW_STATS_TYPE_DELAYED)
#define FLOW_ACTION_HW_STATS_TYPE_DISABLED 0
typedef void (*action_destr)(void *priv); typedef void (*action_destr)(void *priv);
struct flow_action_cookie { struct flow_action_cookie {
@ -168,6 +175,7 @@ void flow_action_cookie_destroy(struct flow_action_cookie *cookie);
struct flow_action_entry { struct flow_action_entry {
enum flow_action_id id; enum flow_action_id id;
u8 hw_stats_type;
action_destr destructor; action_destr destructor;
void *destructor_priv; void *destructor_priv;
union { union {
@ -248,6 +256,66 @@ static inline bool flow_offload_has_one_action(const struct flow_action *action)
return action->num_entries == 1; return action->num_entries == 1;
} }
static inline bool
flow_action_mixed_hw_stats_types_check(const struct flow_action *action,
struct netlink_ext_ack *extack)
{
const struct flow_action_entry *action_entry;
u8 uninitialized_var(last_hw_stats_type);
int i;
if (flow_offload_has_one_action(action))
return true;
for (i = 0; i < action->num_entries; i++) {
action_entry = &action->entries[i];
if (i && action_entry->hw_stats_type != last_hw_stats_type) {
NL_SET_ERR_MSG_MOD(extack, "Mixing HW stats types for actions is not supported");
return false;
}
last_hw_stats_type = action_entry->hw_stats_type;
}
return true;
}
static inline const struct flow_action_entry *
flow_action_first_entry_get(const struct flow_action *action)
{
WARN_ON(!flow_action_has_entries(action));
return &action->entries[0];
}
static inline bool
flow_action_hw_stats_types_check(const struct flow_action *action,
struct netlink_ext_ack *extack,
u8 allowed_hw_stats_type)
{
const struct flow_action_entry *action_entry;
if (!flow_action_has_entries(action))
return true;
if (!flow_action_mixed_hw_stats_types_check(action, extack))
return false;
action_entry = flow_action_first_entry_get(action);
if (allowed_hw_stats_type == 0 &&
action_entry->hw_stats_type != FLOW_ACTION_HW_STATS_TYPE_ANY) {
NL_SET_ERR_MSG_MOD(extack, "Driver supports only default HW stats type \"any\"");
return false;
} else if (allowed_hw_stats_type != 0 &&
action_entry->hw_stats_type != allowed_hw_stats_type) {
NL_SET_ERR_MSG_MOD(extack, "Driver does not support selected HW stats type");
return false;
}
return true;
}
static inline bool
flow_action_basic_hw_stats_types_check(const struct flow_action *action,
struct netlink_ext_ack *extack)
{
return flow_action_hw_stats_types_check(action, extack, 0);
}
#define flow_action_for_each(__i, __act, __actions) \ #define flow_action_for_each(__i, __act, __actions) \
for (__i = 0, __act = &(__actions)->entries[0]; __i < (__actions)->num_entries; __act = &(__actions)->entries[++__i]) for (__i = 0, __act = &(__actions)->entries[0]; __i < (__actions)->num_entries; __act = &(__actions)->entries[++__i])

View File

@ -17,6 +17,7 @@ enum {
TCA_ACT_PAD, TCA_ACT_PAD,
TCA_ACT_COOKIE, TCA_ACT_COOKIE,
TCA_ACT_FLAGS, TCA_ACT_FLAGS,
TCA_ACT_HW_STATS_TYPE,
__TCA_ACT_MAX __TCA_ACT_MAX
}; };
@ -24,6 +25,27 @@ enum {
* actions stats. * actions stats.
*/ */
/* tca HW stats type
* When user does not pass the attribute, he does not care.
* It is the same as if he would pass the attribute with
* all supported bits set.
* In case no bits are set, user is not interested in getting any HW statistics.
*/
#define TCA_ACT_HW_STATS_TYPE_IMMEDIATE (1 << 0) /* Means that in dump, user
* gets the current HW stats
* state from the device
* queried at the dump time.
*/
#define TCA_ACT_HW_STATS_TYPE_DELAYED (1 << 1) /* Means that in dump, user gets
* HW stats that might be out
* of date for some time, maybe
* couple of seconds. This is
* the case when driver polls
* stats updates periodically
* or when it gets async stats update
* from the device.
*/
#define TCA_ACT_MAX __TCA_ACT_MAX #define TCA_ACT_MAX __TCA_ACT_MAX
#define TCA_OLD_COMPAT (TCA_ACT_MAX+1) #define TCA_OLD_COMPAT (TCA_ACT_MAX+1)
#define TCA_ACT_MAX_PRIO 32 #define TCA_ACT_MAX_PRIO 32

View File

@ -865,6 +865,10 @@ static int dsa_slave_add_cls_matchall(struct net_device *dev,
if (!flow_offload_has_one_action(&cls->rule->action)) if (!flow_offload_has_one_action(&cls->rule->action))
return err; return err;
if (!flow_action_basic_hw_stats_types_check(&cls->rule->action,
cls->common.extack))
return err;
act = &cls->rule->action.entries[0]; act = &cls->rule->action.entries[0];
if (act->id == FLOW_ACTION_MIRRED && protocol == htons(ETH_P_ALL)) { if (act->id == FLOW_ACTION_MIRRED && protocol == htons(ETH_P_ALL)) {

View File

@ -185,6 +185,7 @@ static size_t tcf_action_shared_attrs_size(const struct tc_action *act)
return nla_total_size(0) /* action number nested */ return nla_total_size(0) /* action number nested */
+ nla_total_size(IFNAMSIZ) /* TCA_ACT_KIND */ + nla_total_size(IFNAMSIZ) /* TCA_ACT_KIND */
+ cookie_len /* TCA_ACT_COOKIE */ + cookie_len /* TCA_ACT_COOKIE */
+ nla_total_size(sizeof(struct nla_bitfield32)) /* TCA_ACT_HW_STATS_TYPE */
+ nla_total_size(0) /* TCA_ACT_STATS nested */ + nla_total_size(0) /* TCA_ACT_STATS nested */
+ nla_total_size(sizeof(struct nla_bitfield32)) /* TCA_ACT_FLAGS */ + nla_total_size(sizeof(struct nla_bitfield32)) /* TCA_ACT_FLAGS */
/* TCA_STATS_BASIC */ /* TCA_STATS_BASIC */
@ -788,6 +789,17 @@ tcf_action_dump_1(struct sk_buff *skb, struct tc_action *a, int bind, int ref)
} }
rcu_read_unlock(); rcu_read_unlock();
if (a->hw_stats_type != TCA_ACT_HW_STATS_TYPE_ANY) {
struct nla_bitfield32 hw_stats_type = {
a->hw_stats_type,
TCA_ACT_HW_STATS_TYPE_ANY,
};
if (nla_put(skb, TCA_ACT_HW_STATS_TYPE, sizeof(hw_stats_type),
&hw_stats_type))
goto nla_put_failure;
}
if (a->tcfa_flags) { if (a->tcfa_flags) {
struct nla_bitfield32 flags = { a->tcfa_flags, struct nla_bitfield32 flags = { a->tcfa_flags,
a->tcfa_flags, }; a->tcfa_flags, };
@ -854,7 +866,23 @@ static struct tc_cookie *nla_memdup_cookie(struct nlattr **tb)
return c; return c;
} }
static u8 tcf_action_hw_stats_type_get(struct nlattr *hw_stats_type_attr)
{
struct nla_bitfield32 hw_stats_type_bf;
/* If the user did not pass the attr, that means he does
* not care about the type. Return "any" in that case
* which is setting on all supported types.
*/
if (!hw_stats_type_attr)
return TCA_ACT_HW_STATS_TYPE_ANY;
hw_stats_type_bf = nla_get_bitfield32(hw_stats_type_attr);
return hw_stats_type_bf.value;
}
static const u32 tca_act_flags_allowed = TCA_ACT_FLAGS_NO_PERCPU_STATS; static const u32 tca_act_flags_allowed = TCA_ACT_FLAGS_NO_PERCPU_STATS;
static const u32 tca_act_hw_stats_type_allowed = TCA_ACT_HW_STATS_TYPE_ANY;
static const struct nla_policy tcf_action_policy[TCA_ACT_MAX + 1] = { static const struct nla_policy tcf_action_policy[TCA_ACT_MAX + 1] = {
[TCA_ACT_KIND] = { .type = NLA_STRING }, [TCA_ACT_KIND] = { .type = NLA_STRING },
[TCA_ACT_INDEX] = { .type = NLA_U32 }, [TCA_ACT_INDEX] = { .type = NLA_U32 },
@ -863,6 +891,8 @@ static const struct nla_policy tcf_action_policy[TCA_ACT_MAX + 1] = {
[TCA_ACT_OPTIONS] = { .type = NLA_NESTED }, [TCA_ACT_OPTIONS] = { .type = NLA_NESTED },
[TCA_ACT_FLAGS] = { .type = NLA_BITFIELD32, [TCA_ACT_FLAGS] = { .type = NLA_BITFIELD32,
.validation_data = &tca_act_flags_allowed }, .validation_data = &tca_act_flags_allowed },
[TCA_ACT_HW_STATS_TYPE] = { .type = NLA_BITFIELD32,
.validation_data = &tca_act_hw_stats_type_allowed },
}; };
struct tc_action *tcf_action_init_1(struct net *net, struct tcf_proto *tp, struct tc_action *tcf_action_init_1(struct net *net, struct tcf_proto *tp,
@ -871,6 +901,7 @@ struct tc_action *tcf_action_init_1(struct net *net, struct tcf_proto *tp,
bool rtnl_held, bool rtnl_held,
struct netlink_ext_ack *extack) struct netlink_ext_ack *extack)
{ {
u8 hw_stats_type = TCA_ACT_HW_STATS_TYPE_ANY;
struct nla_bitfield32 flags = { 0, 0 }; struct nla_bitfield32 flags = { 0, 0 };
struct tc_action *a; struct tc_action *a;
struct tc_action_ops *a_o; struct tc_action_ops *a_o;
@ -903,6 +934,8 @@ struct tc_action *tcf_action_init_1(struct net *net, struct tcf_proto *tp,
goto err_out; goto err_out;
} }
} }
hw_stats_type =
tcf_action_hw_stats_type_get(tb[TCA_ACT_HW_STATS_TYPE]);
if (tb[TCA_ACT_FLAGS]) if (tb[TCA_ACT_FLAGS])
flags = nla_get_bitfield32(tb[TCA_ACT_FLAGS]); flags = nla_get_bitfield32(tb[TCA_ACT_FLAGS]);
} else { } else {
@ -953,6 +986,9 @@ struct tc_action *tcf_action_init_1(struct net *net, struct tcf_proto *tp,
if (!name && tb[TCA_ACT_COOKIE]) if (!name && tb[TCA_ACT_COOKIE])
tcf_set_action_cookie(&a->act_cookie, cookie); tcf_set_action_cookie(&a->act_cookie, cookie);
if (!name)
a->hw_stats_type = hw_stats_type;
/* module count goes up only when brand new policy is created /* module count goes up only when brand new policy is created
* if it exists and is only bound to in a_o->init() then * if it exists and is only bound to in a_o->init() then
* ACT_P_CREATED is not returned (a zero is). * ACT_P_CREATED is not returned (a zero is).

View File

@ -3464,6 +3464,10 @@ int tc_setup_flow_action(struct flow_action *flow_action,
struct tc_action *act; struct tc_action *act;
int i, j, k, err = 0; int i, j, k, err = 0;
BUILD_BUG_ON(TCA_ACT_HW_STATS_TYPE_ANY != FLOW_ACTION_HW_STATS_TYPE_ANY);
BUILD_BUG_ON(TCA_ACT_HW_STATS_TYPE_IMMEDIATE != FLOW_ACTION_HW_STATS_TYPE_IMMEDIATE);
BUILD_BUG_ON(TCA_ACT_HW_STATS_TYPE_DELAYED != FLOW_ACTION_HW_STATS_TYPE_DELAYED);
if (!exts) if (!exts)
return 0; return 0;
@ -3476,6 +3480,9 @@ int tc_setup_flow_action(struct flow_action *flow_action,
err = tcf_act_get_cookie(entry, act); err = tcf_act_get_cookie(entry, act);
if (err) if (err)
goto err_out_locked; goto err_out_locked;
entry->hw_stats_type = act->hw_stats_type;
if (is_tcf_gact_ok(act)) { if (is_tcf_gact_ok(act)) {
entry->id = FLOW_ACTION_ACCEPT; entry->id = FLOW_ACTION_ACCEPT;
} else if (is_tcf_gact_shot(act)) { } else if (is_tcf_gact_shot(act)) {