// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB // Copyright (c) 2021, NVIDIA CORPORATION & AFFILIATES. All rights reserved. #include "act.h" #include "en/tc_priv.h" #include "fs_core.h" static bool police_act_validate_control(enum flow_action_id act_id, struct netlink_ext_ack *extack) { if (act_id != FLOW_ACTION_PIPE && act_id != FLOW_ACTION_ACCEPT && act_id != FLOW_ACTION_JUMP && act_id != FLOW_ACTION_DROP) { NL_SET_ERR_MSG_MOD(extack, "Offload not supported when conform-exceed action is not pipe, ok, jump or drop"); return false; } return true; } static int police_act_validate(const struct flow_action_entry *act, struct netlink_ext_ack *extack) { if (!police_act_validate_control(act->police.exceed.act_id, extack) || !police_act_validate_control(act->police.notexceed.act_id, extack)) return -EOPNOTSUPP; if (act->police.peakrate_bytes_ps || act->police.avrate || act->police.overhead) { NL_SET_ERR_MSG_MOD(extack, "Offload not supported when peakrate/avrate/overhead is configured"); return -EOPNOTSUPP; } if (act->police.rate_pkt_ps) { NL_SET_ERR_MSG_MOD(extack, "QoS offload not support packets per second"); return -EOPNOTSUPP; } return 0; } static bool tc_act_can_offload_police(struct mlx5e_tc_act_parse_state *parse_state, const struct flow_action_entry *act, int act_index, struct mlx5_flow_attr *attr) { int err; err = police_act_validate(act, parse_state->extack); if (err) return false; return !!mlx5e_get_flow_meters(parse_state->flow->priv->mdev); } static int fill_meter_params_from_act(const struct flow_action_entry *act, struct mlx5e_flow_meter_params *params) { params->index = act->hw_index; if (act->police.rate_bytes_ps) { params->mode = MLX5_RATE_LIMIT_BPS; /* change rate to bits per second */ params->rate = act->police.rate_bytes_ps << 3; params->burst = act->police.burst; } else if (act->police.rate_pkt_ps) { params->mode = MLX5_RATE_LIMIT_PPS; params->rate = act->police.rate_pkt_ps; params->burst = act->police.burst_pkt; } else if (act->police.mtu) { params->mtu = act->police.mtu; } else { return -EOPNOTSUPP; } return 0; } static int tc_act_parse_police(struct mlx5e_tc_act_parse_state *parse_state, const struct flow_action_entry *act, struct mlx5e_priv *priv, struct mlx5_flow_attr *attr) { enum mlx5_flow_namespace_type ns = mlx5e_get_flow_namespace(parse_state->flow); struct mlx5e_flow_meter_params *params = &attr->meter_attr.params; int err; err = fill_meter_params_from_act(act, params); if (err) return err; if (params->mtu) { if (!(mlx5_fs_get_capabilities(priv->mdev, ns) & MLX5_FLOW_STEERING_CAP_MATCH_RANGES)) return -EOPNOTSUPP; attr->action |= MLX5_FLOW_CONTEXT_ACTION_FWD_DEST; attr->flags |= MLX5_ATTR_FLAG_MTU; } else { attr->action |= MLX5_FLOW_CONTEXT_ACTION_EXECUTE_ASO; attr->exe_aso_type = MLX5_EXE_ASO_FLOW_METER; } return 0; } static bool tc_act_is_multi_table_act_police(struct mlx5e_priv *priv, const struct flow_action_entry *act, struct mlx5_flow_attr *attr) { return true; } static int tc_act_police_offload(struct mlx5e_priv *priv, struct flow_offload_action *fl_act, struct flow_action_entry *act) { struct mlx5e_flow_meter_params params = {}; struct mlx5e_flow_meter_handle *meter; int err = 0; err = police_act_validate(act, fl_act->extack); if (err) return err; err = fill_meter_params_from_act(act, ¶ms); if (err) return err; meter = mlx5e_tc_meter_get(priv->mdev, ¶ms); if (IS_ERR(meter) && PTR_ERR(meter) == -ENOENT) { meter = mlx5e_tc_meter_replace(priv->mdev, ¶ms); } else if (!IS_ERR(meter)) { err = mlx5e_tc_meter_update(meter, ¶ms); mlx5e_tc_meter_put(meter); } if (IS_ERR(meter)) { NL_SET_ERR_MSG_MOD(fl_act->extack, "Failed to get flow meter"); mlx5_core_err(priv->mdev, "Failed to get flow meter %d\n", params.index); err = PTR_ERR(meter); } return err; } static int tc_act_police_destroy(struct mlx5e_priv *priv, struct flow_offload_action *fl_act) { struct mlx5e_flow_meter_params params = {}; struct mlx5e_flow_meter_handle *meter; params.index = fl_act->index; meter = mlx5e_tc_meter_get(priv->mdev, ¶ms); if (IS_ERR(meter)) { NL_SET_ERR_MSG_MOD(fl_act->extack, "Failed to get flow meter"); mlx5_core_err(priv->mdev, "Failed to get flow meter %d\n", params.index); return PTR_ERR(meter); } /* first put for the get and second for cleanup */ mlx5e_tc_meter_put(meter); mlx5e_tc_meter_put(meter); return 0; } static int tc_act_police_stats(struct mlx5e_priv *priv, struct flow_offload_action *fl_act) { struct mlx5e_flow_meter_params params = {}; struct mlx5e_flow_meter_handle *meter; u64 bytes, packets, drops, lastuse; params.index = fl_act->index; meter = mlx5e_tc_meter_get(priv->mdev, ¶ms); if (IS_ERR(meter)) { NL_SET_ERR_MSG_MOD(fl_act->extack, "Failed to get flow meter"); mlx5_core_err(priv->mdev, "Failed to get flow meter %d\n", params.index); return PTR_ERR(meter); } mlx5e_tc_meter_get_stats(meter, &bytes, &packets, &drops, &lastuse); flow_stats_update(&fl_act->stats, bytes, packets, drops, lastuse, FLOW_ACTION_HW_STATS_DELAYED); mlx5e_tc_meter_put(meter); return 0; } static bool tc_act_police_get_branch_ctrl(const struct flow_action_entry *act, struct mlx5e_tc_act_branch_ctrl *cond_true, struct mlx5e_tc_act_branch_ctrl *cond_false) { cond_true->act_id = act->police.notexceed.act_id; cond_true->extval = act->police.notexceed.extval; cond_false->act_id = act->police.exceed.act_id; cond_false->extval = act->police.exceed.extval; return true; } struct mlx5e_tc_act mlx5e_tc_act_police = { .can_offload = tc_act_can_offload_police, .parse_action = tc_act_parse_police, .is_multi_table_act = tc_act_is_multi_table_act_police, .offload_action = tc_act_police_offload, .destroy_action = tc_act_police_destroy, .stats_action = tc_act_police_stats, .get_branch_ctrl = tc_act_police_get_branch_ctrl, };