Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  en_tc.c   Sprache: C

 
/*
 * Copyright (c) 2016, Mellanox Technologies. All rights reserved.
 *
 * This software is available to you under a choice of one of two
 * licenses.  You may choose to be licensed under the terms of the GNU
 * General Public License (GPL) Version 2, available from the file
 * COPYING in the main directory of this source tree, or the
 * OpenIB.org BSD license below:
 *
 *     Redistribution and use in source and binary forms, with or
 *     without modification, are permitted provided that the following
 *     conditions are met:
 *
 *      - Redistributions of source code must retain the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer.
 *
 *      - Redistributions in binary form must reproduce the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer in the documentation and/or other materials
 *        provided with the distribution.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */


#include <net/flow_dissector.h>
#include <net/flow_offload.h>
#include <net/sch_generic.h>
#include <net/pkt_cls.h>
#include <linux/mlx5/fs.h>
#include <linux/mlx5/device.h>
#include <linux/rhashtable.h>
#include <linux/refcount.h>
#include <linux/completion.h>
#include <net/arp.h>
#include <net/ipv6_stubs.h>
#include <net/bareudp.h>
#include <net/bonding.h>
#include <net/dst_metadata.h>
#include "devlink.h"
#include "en.h"
#include "en/tc/post_act.h"
#include "en/tc/act_stats.h"
#include "en_rep.h"
#include "en/rep/tc.h"
#include "en/rep/neigh.h"
#include "en_tc.h"
#include "eswitch.h"
#include "fs_core.h"
#include "en/port.h"
#include "en/tc_tun.h"
#include "en/mapping.h"
#include "en/tc_ct.h"
#include "en/mod_hdr.h"
#include "en/tc_tun_encap.h"
#include "en/tc/sample.h"
#include "en/tc/act/act.h"
#include "en/tc/post_meter.h"
#include "lib/devcom.h"
#include "lib/geneve.h"
#include "lib/fs_chains.h"
#include "diag/en_tc_tracepoint.h"
#include <asm/div64.h>
#include "lag/lag.h"
#include "lag/mp.h"

#define MLX5E_TC_TABLE_NUM_GROUPS 4
#define MLX5E_TC_TABLE_MAX_GROUP_SIZE BIT(18)

struct mlx5e_tc_table {
 /* Protects the dynamic assignment of the t parameter
 * which is the nic tc root table.
 */

 struct mutex   t_lock;
 struct mlx5e_priv  *priv;
 struct mlx5_flow_table  *t;
 struct mlx5_flow_table  *miss_t;
 struct mlx5_fs_chains           *chains;
 struct mlx5e_post_act  *post_act;

 struct rhashtable               ht;

 struct mod_hdr_tbl mod_hdr;
 struct mutex hairpin_tbl_lock; /* protects hairpin_tbl */
 DECLARE_HASHTABLE(hairpin_tbl, 8);

 struct notifier_block     netdevice_nb;
 struct netdev_net_notifier netdevice_nn;

 struct mlx5_tc_ct_priv         *ct;
 struct mapping_ctx             *mapping;
 struct dentry                  *dfs_root;

 /* tc action stats */
 struct mlx5e_tc_act_stats_handle *action_stats_handle;
};

struct mlx5e_tc_attr_to_reg_mapping mlx5e_tc_attr_to_reg_mappings[] = {
 [MAPPED_OBJ_TO_REG] = {
  .mfield = MLX5_ACTION_IN_FIELD_METADATA_REG_C_0,
  .moffset = 0,
  .mlen = 16,
 },
 [VPORT_TO_REG] = {
  .mfield = MLX5_ACTION_IN_FIELD_METADATA_REG_C_0,
  .moffset = 16,
  .mlen = 16,
 },
 [TUNNEL_TO_REG] = {
  .mfield = MLX5_ACTION_IN_FIELD_METADATA_REG_C_1,
  .moffset = 8,
  .mlen = ESW_TUN_OPTS_BITS + ESW_TUN_ID_BITS,
  .soffset = MLX5_BYTE_OFF(fte_match_param,
      misc_parameters_2.metadata_reg_c_1),
 },
 [ZONE_TO_REG] = zone_to_reg_ct,
 [ZONE_RESTORE_TO_REG] = zone_restore_to_reg_ct,
 [CTSTATE_TO_REG] = ctstate_to_reg_ct,
 [MARK_TO_REG] = mark_to_reg_ct,
 [LABELS_TO_REG] = labels_to_reg_ct,
 [FTEID_TO_REG] = fteid_to_reg_ct,
 /* For NIC rules we store the restore metadata directly
 * into reg_b that is passed to SW since we don't
 * jump between steering domains.
 */

 [NIC_MAPPED_OBJ_TO_REG] = {
  .mfield = MLX5_ACTION_IN_FIELD_METADATA_REG_B,
  .moffset = 0,
  .mlen = 16,
 },
 [NIC_ZONE_RESTORE_TO_REG] = nic_zone_restore_to_reg_ct,
 [PACKET_COLOR_TO_REG] = packet_color_to_reg,
};

struct mlx5e_tc_jump_state {
 u32 jump_count;
 bool jump_target;
 struct mlx5_flow_attr *jumping_attr;

 enum flow_action_id last_id;
 u32 last_index;
};

struct mlx5e_tc_table *mlx5e_tc_table_alloc(void)
{
 struct mlx5e_tc_table *tc;

 tc = kvzalloc(sizeof(*tc), GFP_KERNEL);
 return tc ? tc : ERR_PTR(-ENOMEM);
}

void mlx5e_tc_table_free(struct mlx5e_tc_table *tc)
{
 kvfree(tc);
}

struct mlx5_fs_chains *mlx5e_nic_chains(struct mlx5e_tc_table *tc)
{
 return tc->chains;
}

/* To avoid false lock dependency warning set the tc_ht lock
 * class different than the lock class of the ht being used when deleting
 * last flow from a group and then deleting a group, we get into del_sw_flow_group()
 * which call rhashtable_destroy on fg->ftes_hash which will take ht->mutex but
 * it's different than the ht->mutex here.
 */

static struct lock_class_key tc_ht_lock_key;
static struct lock_class_key tc_ht_wq_key;

static void mlx5e_put_flow_tunnel_id(struct mlx5e_tc_flow *flow);
static void free_flow_post_acts(struct mlx5e_tc_flow *flow);
static void mlx5_free_flow_attr_actions(struct mlx5e_tc_flow *flow,
     struct mlx5_flow_attr *attr);

void
mlx5e_tc_match_to_reg_match(struct mlx5_flow_spec *spec,
       enum mlx5e_tc_attr_to_reg type,
       u32 val,
       u32 mask)
{
 void *headers_c = spec->match_criteria, *headers_v = spec->match_value, *fmask, *fval;
 int soffset = mlx5e_tc_attr_to_reg_mappings[type].soffset;
 int moffset = mlx5e_tc_attr_to_reg_mappings[type].moffset;
 int match_len = mlx5e_tc_attr_to_reg_mappings[type].mlen;
 u32 max_mask = GENMASK(match_len - 1, 0);
 __be32 curr_mask_be, curr_val_be;
 u32 curr_mask, curr_val;

 fmask = headers_c + soffset;
 fval = headers_v + soffset;

 memcpy(&curr_mask_be, fmask, 4);
 memcpy(&curr_val_be, fval, 4);

 curr_mask = be32_to_cpu(curr_mask_be);
 curr_val = be32_to_cpu(curr_val_be);

 //move to correct offset
 WARN_ON(mask > max_mask);
 mask <<= moffset;
 val <<= moffset;
 max_mask <<= moffset;

 //zero val and mask
 curr_mask &= ~max_mask;
 curr_val &= ~max_mask;

 //add current to mask
 curr_mask |= mask;
 curr_val |= val;

 //back to be32 and write
 curr_mask_be = cpu_to_be32(curr_mask);
 curr_val_be = cpu_to_be32(curr_val);

 memcpy(fmask, &curr_mask_be, 4);
 memcpy(fval, &curr_val_be, 4);

 spec->match_criteria_enable |= MLX5_MATCH_MISC_PARAMETERS_2;
}

void
mlx5e_tc_match_to_reg_get_match(struct mlx5_flow_spec *spec,
    enum mlx5e_tc_attr_to_reg type,
    u32 *val,
    u32 *mask)
{
 void *headers_c = spec->match_criteria, *headers_v = spec->match_value, *fmask, *fval;
 int soffset = mlx5e_tc_attr_to_reg_mappings[type].soffset;
 int moffset = mlx5e_tc_attr_to_reg_mappings[type].moffset;
 int match_len = mlx5e_tc_attr_to_reg_mappings[type].mlen;
 u32 max_mask = GENMASK(match_len - 1, 0);
 __be32 curr_mask_be, curr_val_be;
 u32 curr_mask, curr_val;

 fmask = headers_c + soffset;
 fval = headers_v + soffset;

 memcpy(&curr_mask_be, fmask, 4);
 memcpy(&curr_val_be, fval, 4);

 curr_mask = be32_to_cpu(curr_mask_be);
 curr_val = be32_to_cpu(curr_val_be);

 *mask = (curr_mask >> moffset) & max_mask;
 *val = (curr_val >> moffset) & max_mask;
}

int
mlx5e_tc_match_to_reg_set_and_get_id(struct mlx5_core_dev *mdev,
         struct mlx5e_tc_mod_hdr_acts *mod_hdr_acts,
         enum mlx5_flow_namespace_type ns,
         enum mlx5e_tc_attr_to_reg type,
         u32 data)
{
 int moffset = mlx5e_tc_attr_to_reg_mappings[type].moffset;
 int mfield = mlx5e_tc_attr_to_reg_mappings[type].mfield;
 int mlen = mlx5e_tc_attr_to_reg_mappings[type].mlen;
 char *modact;
 int err;

 modact = mlx5e_mod_hdr_alloc(mdev, ns, mod_hdr_acts);
 if (IS_ERR(modact))
  return PTR_ERR(modact);

 /* Firmware has 5bit length field and 0 means 32bits */
 if (mlen == 32)
  mlen = 0;

 MLX5_SET(set_action_in, modact, action_type, MLX5_ACTION_TYPE_SET);
 MLX5_SET(set_action_in, modact, field, mfield);
 MLX5_SET(set_action_in, modact, offset, moffset);
 MLX5_SET(set_action_in, modact, length, mlen);
 MLX5_SET(set_action_in, modact, data, data);
 err = mod_hdr_acts->num_actions;
 mod_hdr_acts->num_actions++;

 return err;
}

static struct mlx5e_tc_act_stats_handle  *
get_act_stats_handle(struct mlx5e_priv *priv)
{
 struct mlx5e_tc_table *tc = mlx5e_fs_get_tc(priv->fs);
 struct mlx5_eswitch *esw = priv->mdev->priv.eswitch;
 struct mlx5_rep_uplink_priv *uplink_priv;
 struct mlx5e_rep_priv *uplink_rpriv;

 if (is_mdev_switchdev_mode(priv->mdev)) {
  uplink_rpriv = mlx5_eswitch_get_uplink_priv(esw, REP_ETH);
  uplink_priv = &uplink_rpriv->uplink_priv;

  return uplink_priv->action_stats_handle;
 }

 return tc->action_stats_handle;
}

struct mlx5e_tc_int_port_priv *
mlx5e_get_int_port_priv(struct mlx5e_priv *priv)
{
 struct mlx5_eswitch *esw = priv->mdev->priv.eswitch;
 struct mlx5_rep_uplink_priv *uplink_priv;
 struct mlx5e_rep_priv *uplink_rpriv;

 if (is_mdev_switchdev_mode(priv->mdev)) {
  uplink_rpriv = mlx5_eswitch_get_uplink_priv(esw, REP_ETH);
  uplink_priv = &uplink_rpriv->uplink_priv;

  return uplink_priv->int_port_priv;
 }

 return NULL;
}

struct mlx5e_flow_meters *
mlx5e_get_flow_meters(struct mlx5_core_dev *dev)
{
 struct mlx5_eswitch *esw = dev->priv.eswitch;
 struct mlx5_rep_uplink_priv *uplink_priv;
 struct mlx5e_rep_priv *uplink_rpriv;
 struct mlx5e_priv *priv;

 if (is_mdev_switchdev_mode(dev)) {
  uplink_rpriv = mlx5_eswitch_get_uplink_priv(esw, REP_ETH);
  uplink_priv = &uplink_rpriv->uplink_priv;
  priv = netdev_priv(uplink_rpriv->netdev);
  if (!uplink_priv->flow_meters)
   uplink_priv->flow_meters =
    mlx5e_flow_meters_init(priv,
             MLX5_FLOW_NAMESPACE_FDB,
             uplink_priv->post_act);
  if (!IS_ERR(uplink_priv->flow_meters))
   return uplink_priv->flow_meters;
 }

 return NULL;
}

static struct mlx5_tc_ct_priv *
get_ct_priv(struct mlx5e_priv *priv)
{
 struct mlx5e_tc_table *tc = mlx5e_fs_get_tc(priv->fs);
 struct mlx5_eswitch *esw = priv->mdev->priv.eswitch;
 struct mlx5_rep_uplink_priv *uplink_priv;
 struct mlx5e_rep_priv *uplink_rpriv;

 if (is_mdev_switchdev_mode(priv->mdev)) {
  uplink_rpriv = mlx5_eswitch_get_uplink_priv(esw, REP_ETH);
  uplink_priv = &uplink_rpriv->uplink_priv;

  return uplink_priv->ct_priv;
 }

 return tc->ct;
}

static struct mlx5e_tc_psample *
get_sample_priv(struct mlx5e_priv *priv)
{
 struct mlx5_eswitch *esw = priv->mdev->priv.eswitch;
 struct mlx5_rep_uplink_priv *uplink_priv;
 struct mlx5e_rep_priv *uplink_rpriv;

 if (is_mdev_switchdev_mode(priv->mdev)) {
  uplink_rpriv = mlx5_eswitch_get_uplink_priv(esw, REP_ETH);
  uplink_priv = &uplink_rpriv->uplink_priv;

  return uplink_priv->tc_psample;
 }

 return NULL;
}

static struct mlx5e_post_act *
get_post_action(struct mlx5e_priv *priv)
{
 struct mlx5e_tc_table *tc = mlx5e_fs_get_tc(priv->fs);
 struct mlx5_eswitch *esw = priv->mdev->priv.eswitch;
 struct mlx5_rep_uplink_priv *uplink_priv;
 struct mlx5e_rep_priv *uplink_rpriv;

 if (is_mdev_switchdev_mode(priv->mdev)) {
  uplink_rpriv = mlx5_eswitch_get_uplink_priv(esw, REP_ETH);
  uplink_priv = &uplink_rpriv->uplink_priv;

  return uplink_priv->post_act;
 }

 return tc->post_act;
}

struct mlx5_flow_handle *
mlx5_tc_rule_insert(struct mlx5e_priv *priv,
      struct mlx5_flow_spec *spec,
      struct mlx5_flow_attr *attr)
{
 struct mlx5_eswitch *esw = priv->mdev->priv.eswitch;

 if (is_mdev_switchdev_mode(priv->mdev))
  return mlx5_eswitch_add_offloaded_rule(esw, spec, attr);

 return mlx5e_add_offloaded_nic_rule(priv, spec, attr);
}

void
mlx5_tc_rule_delete(struct mlx5e_priv *priv,
      struct mlx5_flow_handle *rule,
      struct mlx5_flow_attr *attr)
{
 struct mlx5_eswitch *esw = priv->mdev->priv.eswitch;

 if (is_mdev_switchdev_mode(priv->mdev)) {
  mlx5_eswitch_del_offloaded_rule(esw, rule, attr);
  return;
 }

 mlx5e_del_offloaded_nic_rule(priv, rule, attr);
}

static bool
is_flow_meter_action(struct mlx5_flow_attr *attr)
{
 return (((attr->action & MLX5_FLOW_CONTEXT_ACTION_EXECUTE_ASO) &&
   (attr->exe_aso_type == MLX5_EXE_ASO_FLOW_METER)) ||
  attr->flags & MLX5_ATTR_FLAG_MTU);
}

static int
mlx5e_tc_add_flow_meter(struct mlx5e_priv *priv,
   struct mlx5_flow_attr *attr)
{
 struct mlx5e_post_act *post_act = get_post_action(priv);
 struct mlx5e_post_meter_priv *post_meter;
 enum mlx5_flow_namespace_type ns_type;
 struct mlx5e_flow_meter_handle *meter;
 enum mlx5e_post_meter_type type;

 if (IS_ERR(post_act))
  return PTR_ERR(post_act);

 meter = mlx5e_tc_meter_replace(priv->mdev, &attr->meter_attr.params);
 if (IS_ERR(meter)) {
  mlx5_core_err(priv->mdev, "Failed to get flow meter\n");
  return PTR_ERR(meter);
 }

 ns_type = mlx5e_tc_meter_get_namespace(meter->flow_meters);
 type = meter->params.mtu ? MLX5E_POST_METER_MTU : MLX5E_POST_METER_RATE;
 post_meter = mlx5e_post_meter_init(priv, ns_type, post_act,
        type,
        meter->act_counter, meter->drop_counter,
        attr->branch_true, attr->branch_false);
 if (IS_ERR(post_meter)) {
  mlx5_core_err(priv->mdev, "Failed to init post meter\n");
  goto err_meter_init;
 }

 attr->meter_attr.meter = meter;
 attr->meter_attr.post_meter = post_meter;
 attr->dest_ft = mlx5e_post_meter_get_ft(post_meter);
 attr->action |= MLX5_FLOW_CONTEXT_ACTION_FWD_DEST;

 return 0;

err_meter_init:
 mlx5e_tc_meter_put(meter);
 return PTR_ERR(post_meter);
}

static void
mlx5e_tc_del_flow_meter(struct mlx5_eswitch *esw, struct mlx5_flow_attr *attr)
{
 mlx5e_post_meter_cleanup(esw, attr->meter_attr.post_meter);
 mlx5e_tc_meter_put(attr->meter_attr.meter);
}

struct mlx5_flow_handle *
mlx5e_tc_rule_offload(struct mlx5e_priv *priv,
        struct mlx5_flow_spec *spec,
        struct mlx5_flow_attr *attr)
{
 struct mlx5_eswitch *esw = priv->mdev->priv.eswitch;
 int err;

 if (!is_mdev_switchdev_mode(priv->mdev))
  return mlx5e_add_offloaded_nic_rule(priv, spec, attr);

 if (attr->flags & MLX5_ATTR_FLAG_SAMPLE)
  return mlx5e_tc_sample_offload(get_sample_priv(priv), spec, attr);

 if (is_flow_meter_action(attr)) {
  err = mlx5e_tc_add_flow_meter(priv, attr);
  if (err)
   return ERR_PTR(err);
 }

 return mlx5_eswitch_add_offloaded_rule(esw, spec, attr);
}

void
mlx5e_tc_rule_unoffload(struct mlx5e_priv *priv,
   struct mlx5_flow_handle *rule,
   struct mlx5_flow_attr *attr)
{
 struct mlx5_eswitch *esw = priv->mdev->priv.eswitch;

 if (!is_mdev_switchdev_mode(priv->mdev)) {
  mlx5e_del_offloaded_nic_rule(priv, rule, attr);
  return;
 }

 if (attr->flags & MLX5_ATTR_FLAG_SAMPLE) {
  mlx5e_tc_sample_unoffload(get_sample_priv(priv), rule, attr);
  return;
 }

 mlx5_eswitch_del_offloaded_rule(esw, rule, attr);

 if (attr->meter_attr.meter)
  mlx5e_tc_del_flow_meter(esw, attr);
}

int
mlx5e_tc_match_to_reg_set(struct mlx5_core_dev *mdev,
     struct mlx5e_tc_mod_hdr_acts *mod_hdr_acts,
     enum mlx5_flow_namespace_type ns,
     enum mlx5e_tc_attr_to_reg type,
     u32 data)
{
 int ret = mlx5e_tc_match_to_reg_set_and_get_id(mdev, mod_hdr_acts, ns, type, data);

 return ret < 0 ? ret : 0;
}

void mlx5e_tc_match_to_reg_mod_hdr_change(struct mlx5_core_dev *mdev,
       struct mlx5e_tc_mod_hdr_acts *mod_hdr_acts,
       enum mlx5e_tc_attr_to_reg type,
       int act_id, u32 data)
{
 int moffset = mlx5e_tc_attr_to_reg_mappings[type].moffset;
 int mfield = mlx5e_tc_attr_to_reg_mappings[type].mfield;
 int mlen = mlx5e_tc_attr_to_reg_mappings[type].mlen;
 char *modact;

 modact = mlx5e_mod_hdr_get_item(mod_hdr_acts, act_id);

 /* Firmware has 5bit length field and 0 means 32bits */
 if (mlen == 32)
  mlen = 0;

 MLX5_SET(set_action_in, modact, action_type, MLX5_ACTION_TYPE_SET);
 MLX5_SET(set_action_in, modact, field, mfield);
 MLX5_SET(set_action_in, modact, offset, moffset);
 MLX5_SET(set_action_in, modact, length, mlen);
 MLX5_SET(set_action_in, modact, data, data);
}

struct mlx5e_hairpin {
 struct mlx5_hairpin *pair;

 struct mlx5_core_dev *func_mdev;
 struct mlx5e_priv *func_priv;
 u32 tdn;
 struct mlx5e_tir direct_tir;

 int num_channels;
 u8 log_num_packets;
 struct mlx5e_rqt indir_rqt;
 struct mlx5e_tir indir_tir[MLX5E_NUM_INDIR_TIRS];
 struct mlx5_ttc_table *ttc;
};

struct mlx5e_hairpin_entry {
 /* a node of a hash table which keeps all the  hairpin entries */
 struct hlist_node hairpin_hlist;

 /* protects flows list */
 spinlock_t flows_lock;
 /* flows sharing the same hairpin */
 struct list_head flows;
 /* hpe's that were not fully initialized when dead peer update event
 * function traversed them.
 */

 struct list_head dead_peer_wait_list;

 u16 peer_vhca_id;
 u8 prio;
 struct mlx5e_hairpin *hp;
 refcount_t refcnt;
 struct completion res_ready;
};

static void mlx5e_tc_del_flow(struct mlx5e_priv *priv,
         struct mlx5e_tc_flow *flow);

struct mlx5e_tc_flow *mlx5e_flow_get(struct mlx5e_tc_flow *flow)
{
 if (!flow || !refcount_inc_not_zero(&flow->refcnt))
  return ERR_PTR(-EINVAL);
 return flow;
}

void mlx5e_flow_put(struct mlx5e_priv *priv, struct mlx5e_tc_flow *flow)
{
 if (refcount_dec_and_test(&flow->refcnt)) {
  mlx5e_tc_del_flow(priv, flow);
  kfree_rcu(flow, rcu_head);
 }
}

bool mlx5e_is_eswitch_flow(struct mlx5e_tc_flow *flow)
{
 return flow_flag_test(flow, ESWITCH);
}

bool mlx5e_is_ft_flow(struct mlx5e_tc_flow *flow)
{
 return flow_flag_test(flow, FT);
}

bool mlx5e_is_offloaded_flow(struct mlx5e_tc_flow *flow)
{
 return flow_flag_test(flow, OFFLOADED);
}

int mlx5e_get_flow_namespace(struct mlx5e_tc_flow *flow)
{
 return mlx5e_is_eswitch_flow(flow) ?
  MLX5_FLOW_NAMESPACE_FDB : MLX5_FLOW_NAMESPACE_KERNEL;
}

static struct mlx5_core_dev *
get_flow_counter_dev(struct mlx5e_tc_flow *flow)
{
 return mlx5e_is_eswitch_flow(flow) ? flow->attr->esw_attr->counter_dev : flow->priv->mdev;
}

static struct mod_hdr_tbl *
get_mod_hdr_table(struct mlx5e_priv *priv, struct mlx5e_tc_flow *flow)
{
 struct mlx5e_tc_table *tc = mlx5e_fs_get_tc(priv->fs);
 struct mlx5_eswitch *esw = priv->mdev->priv.eswitch;

 return mlx5e_get_flow_namespace(flow) == MLX5_FLOW_NAMESPACE_FDB ?
  &esw->offloads.mod_hdr :
  &tc->mod_hdr;
}

int mlx5e_tc_attach_mod_hdr(struct mlx5e_priv *priv,
       struct mlx5e_tc_flow *flow,
       struct mlx5_flow_attr *attr)
{
 struct mlx5e_mod_hdr_handle *mh;

 mh = mlx5e_mod_hdr_attach(priv->mdev, get_mod_hdr_table(priv, flow),
      mlx5e_get_flow_namespace(flow),
      &attr->parse_attr->mod_hdr_acts);
 if (IS_ERR(mh))
  return PTR_ERR(mh);

 WARN_ON(attr->modify_hdr);
 attr->modify_hdr = mlx5e_mod_hdr_get(mh);
 attr->mh = mh;

 return 0;
}

void mlx5e_tc_detach_mod_hdr(struct mlx5e_priv *priv,
        struct mlx5e_tc_flow *flow,
        struct mlx5_flow_attr *attr)
{
 /* flow wasn't fully initialized */
 if (!attr->mh)
  return;

 mlx5e_mod_hdr_detach(priv->mdev, get_mod_hdr_table(priv, flow),
        attr->mh);
 attr->mh = NULL;
}

static
struct mlx5_core_dev *mlx5e_hairpin_get_mdev(struct net *net, int ifindex)
{
 struct mlx5_core_dev *mdev;
 struct net_device *netdev;
 struct mlx5e_priv *priv;

 netdev = dev_get_by_index(net, ifindex);
 if (!netdev)
  return ERR_PTR(-ENODEV);

 priv = netdev_priv(netdev);
 mdev = priv->mdev;
 dev_put(netdev);

 /* Mirred tc action holds a refcount on the ifindex net_device (see
 * net/sched/act_mirred.c:tcf_mirred_get_dev). So, it's okay to continue using mdev
 * after dev_put(netdev), while we're in the context of adding a tc flow.
 *
 * The mdev pointer corresponds to the peer/out net_device of a hairpin. It is then
 * stored in a hairpin object, which exists until all flows, that refer to it, get
 * removed.
 *
 * On the other hand, after a hairpin object has been created, the peer net_device may
 * be removed/unbound while there are still some hairpin flows that are using it. This
 * case is handled by mlx5e_tc_hairpin_update_dead_peer, which is hooked to
 * NETDEV_UNREGISTER event of the peer net_device.
 */

 return mdev;
}

static int mlx5e_hairpin_create_transport(struct mlx5e_hairpin *hp)
{
 struct mlx5e_tir_builder *builder;
 int err;

 builder = mlx5e_tir_builder_alloc(false);
 if (!builder)
  return -ENOMEM;

 err = mlx5_core_alloc_transport_domain(hp->func_mdev, &hp->tdn);
 if (err)
  goto out;

 mlx5e_tir_builder_build_inline(builder, hp->tdn, hp->pair->rqn[0]);
 err = mlx5e_tir_init(&hp->direct_tir, builder, hp->func_mdev, false);
 if (err)
  goto create_tir_err;

out:
 mlx5e_tir_builder_free(builder);
 return err;

create_tir_err:
 mlx5_core_dealloc_transport_domain(hp->func_mdev, hp->tdn);

 goto out;
}

static void mlx5e_hairpin_destroy_transport(struct mlx5e_hairpin *hp)
{
 mlx5e_tir_destroy(&hp->direct_tir);
 mlx5_core_dealloc_transport_domain(hp->func_mdev, hp->tdn);
}

static int mlx5e_hairpin_create_indirect_rqt(struct mlx5e_hairpin *hp)
{
 struct mlx5e_priv *priv = hp->func_priv;
 struct mlx5_core_dev *mdev = priv->mdev;
 struct mlx5e_rss_params_indir indir;
 int err;

 err = mlx5e_rss_params_indir_init(&indir, mdev,
       mlx5e_rqt_size(mdev, hp->num_channels),
       mlx5e_rqt_size(mdev, hp->num_channels));
 if (err)
  return err;

 mlx5e_rss_params_indir_init_uniform(&indir, hp->num_channels);
 err = mlx5e_rqt_init_indir(&hp->indir_rqt, mdev, hp->pair->rqn, NULL, hp->num_channels,
       mlx5e_rx_res_get_current_hash(priv->rx_res).hfunc,
       &indir);

 mlx5e_rss_params_indir_cleanup(&indir);
 return err;
}

static int mlx5e_hairpin_create_indirect_tirs(struct mlx5e_hairpin *hp)
{
 struct mlx5e_priv *priv = hp->func_priv;
 struct mlx5e_rss_params_hash rss_hash;
 enum mlx5_traffic_types tt, max_tt;
 struct mlx5e_tir_builder *builder;
 int err = 0;

 builder = mlx5e_tir_builder_alloc(false);
 if (!builder)
  return -ENOMEM;

 rss_hash = mlx5e_rx_res_get_current_hash(priv->rx_res);

 for (tt = 0; tt < MLX5E_NUM_INDIR_TIRS; tt++) {
  struct mlx5e_rss_params_traffic_type rss_tt;

  rss_tt = mlx5e_rss_get_default_tt_config(tt);

  mlx5e_tir_builder_build_rqt(builder, hp->tdn,
         mlx5e_rqt_get_rqtn(&hp->indir_rqt),
         false);
  mlx5e_tir_builder_build_rss(builder, &rss_hash, &rss_tt, false);

  err = mlx5e_tir_init(&hp->indir_tir[tt], builder, hp->func_mdev, false);
  if (err) {
   mlx5_core_warn(hp->func_mdev, "create indirect tirs failed, %d\n", err);
   goto err_destroy_tirs;
  }

  mlx5e_tir_builder_clear(builder);
 }

out:
 mlx5e_tir_builder_free(builder);
 return err;

err_destroy_tirs:
 max_tt = tt;
 for (tt = 0; tt < max_tt; tt++)
  mlx5e_tir_destroy(&hp->indir_tir[tt]);

 goto out;
}

static void mlx5e_hairpin_destroy_indirect_tirs(struct mlx5e_hairpin *hp)
{
 int tt;

 for (tt = 0; tt < MLX5E_NUM_INDIR_TIRS; tt++)
  mlx5e_tir_destroy(&hp->indir_tir[tt]);
}

static void mlx5e_hairpin_set_ttc_params(struct mlx5e_hairpin *hp,
      struct ttc_params *ttc_params)
{
 struct mlx5_flow_table_attr *ft_attr = &ttc_params->ft_attr;
 int tt;

 memset(ttc_params, 0, sizeof(*ttc_params));

 ttc_params->ns_type = MLX5_FLOW_NAMESPACE_KERNEL;
 for (tt = 0; tt < MLX5_NUM_TT; tt++) {
  ttc_params->dests[tt].type = MLX5_FLOW_DESTINATION_TYPE_TIR;
  ttc_params->dests[tt].tir_num =
   tt == MLX5_TT_ANY ?
    mlx5e_tir_get_tirn(&hp->direct_tir) :
    mlx5e_tir_get_tirn(&hp->indir_tir[tt]);
 }

 ft_attr->level = MLX5E_TC_TTC_FT_LEVEL;
 ft_attr->prio = MLX5E_TC_PRIO;
}

static int mlx5e_hairpin_rss_init(struct mlx5e_hairpin *hp)
{
 struct mlx5e_priv *priv = hp->func_priv;
 struct ttc_params ttc_params;
 struct mlx5_ttc_table *ttc;
 int err;

 err = mlx5e_hairpin_create_indirect_rqt(hp);
 if (err)
  return err;

 err = mlx5e_hairpin_create_indirect_tirs(hp);
 if (err)
  goto err_create_indirect_tirs;

 mlx5e_hairpin_set_ttc_params(hp, &ttc_params);
 hp->ttc = mlx5_create_ttc_table(priv->mdev, &ttc_params);
 if (IS_ERR(hp->ttc)) {
  err = PTR_ERR(hp->ttc);
  goto err_create_ttc_table;
 }

 ttc = mlx5e_fs_get_ttc(priv->fs, false);
 netdev_dbg(priv->netdev, "add hairpin: using %d channels rss ttc table id %x\n",
     hp->num_channels,
     mlx5_get_ttc_flow_table(ttc)->id);

 return 0;

err_create_ttc_table:
 mlx5e_hairpin_destroy_indirect_tirs(hp);
err_create_indirect_tirs:
 mlx5e_rqt_destroy(&hp->indir_rqt);

 return err;
}

static void mlx5e_hairpin_rss_cleanup(struct mlx5e_hairpin *hp)
{
 mlx5_destroy_ttc_table(hp->ttc);
 mlx5e_hairpin_destroy_indirect_tirs(hp);
 mlx5e_rqt_destroy(&hp->indir_rqt);
}

static struct mlx5e_hairpin *
mlx5e_hairpin_create(struct mlx5e_priv *priv, struct mlx5_hairpin_params *params,
       int peer_ifindex)
{
 struct mlx5_core_dev *func_mdev, *peer_mdev;
 struct mlx5e_hairpin *hp;
 struct mlx5_hairpin *pair;
 int err;

 hp = kzalloc(sizeof(*hp), GFP_KERNEL);
 if (!hp)
  return ERR_PTR(-ENOMEM);

 func_mdev = priv->mdev;
 peer_mdev = mlx5e_hairpin_get_mdev(dev_net(priv->netdev), peer_ifindex);
 if (IS_ERR(peer_mdev)) {
  err = PTR_ERR(peer_mdev);
  goto create_pair_err;
 }

 pair = mlx5_core_hairpin_create(func_mdev, peer_mdev, params);
 if (IS_ERR(pair)) {
  err = PTR_ERR(pair);
  goto create_pair_err;
 }
 hp->pair = pair;
 hp->func_mdev = func_mdev;
 hp->func_priv = priv;
 hp->num_channels = params->num_channels;
 hp->log_num_packets = params->log_num_packets;

 err = mlx5e_hairpin_create_transport(hp);
 if (err)
  goto create_transport_err;

 if (hp->num_channels > 1) {
  err = mlx5e_hairpin_rss_init(hp);
  if (err)
   goto rss_init_err;
 }

 return hp;

rss_init_err:
 mlx5e_hairpin_destroy_transport(hp);
create_transport_err:
 mlx5_core_hairpin_destroy(hp->pair);
create_pair_err:
 kfree(hp);
 return ERR_PTR(err);
}

static void mlx5e_hairpin_destroy(struct mlx5e_hairpin *hp)
{
 if (hp->num_channels > 1)
  mlx5e_hairpin_rss_cleanup(hp);
 mlx5e_hairpin_destroy_transport(hp);
 mlx5_core_hairpin_destroy(hp->pair);
 kvfree(hp);
}

static inline u32 hash_hairpin_info(u16 peer_vhca_id, u8 prio)
{
 return (peer_vhca_id << 16 | prio);
}

static struct mlx5e_hairpin_entry *mlx5e_hairpin_get(struct mlx5e_priv *priv,
           u16 peer_vhca_id, u8 prio)
{
 struct mlx5e_tc_table *tc = mlx5e_fs_get_tc(priv->fs);
 struct mlx5e_hairpin_entry *hpe;
 u32 hash_key = hash_hairpin_info(peer_vhca_id, prio);

 hash_for_each_possible(tc->hairpin_tbl, hpe,
          hairpin_hlist, hash_key) {
  if (hpe->peer_vhca_id == peer_vhca_id && hpe->prio == prio) {
   refcount_inc(&hpe->refcnt);
   return hpe;
  }
 }

 return NULL;
}

static void mlx5e_hairpin_put(struct mlx5e_priv *priv,
         struct mlx5e_hairpin_entry *hpe)
{
 struct mlx5e_tc_table *tc = mlx5e_fs_get_tc(priv->fs);
 /* no more hairpin flows for us, release the hairpin pair */
 if (!refcount_dec_and_mutex_lock(&hpe->refcnt, &tc->hairpin_tbl_lock))
  return;
 hash_del(&hpe->hairpin_hlist);
 mutex_unlock(&tc->hairpin_tbl_lock);

 if (!IS_ERR_OR_NULL(hpe->hp)) {
  netdev_dbg(priv->netdev, "del hairpin: peer %s\n",
      dev_name(hpe->hp->pair->peer_mdev->device));

  mlx5e_hairpin_destroy(hpe->hp);
 }

 WARN_ON(!list_empty(&hpe->flows));
 kfree(hpe);
}

#define UNKNOWN_MATCH_PRIO 8

static int mlx5e_hairpin_get_prio(struct mlx5e_priv *priv,
      struct mlx5_flow_spec *spec, u8 *match_prio,
      struct netlink_ext_ack *extack)
{
 void *headers_c, *headers_v;
 u8 prio_val, prio_mask = 0;
 bool vlan_present;

#ifdef CONFIG_MLX5_CORE_EN_DCB
 if (priv->dcbx_dp.trust_state != MLX5_QPTS_TRUST_PCP) {
  NL_SET_ERR_MSG_MOD(extack,
       "only PCP trust state supported for hairpin");
  return -EOPNOTSUPP;
 }
#endif
 headers_c = MLX5_ADDR_OF(fte_match_param, spec->match_criteria, outer_headers);
 headers_v = MLX5_ADDR_OF(fte_match_param, spec->match_value, outer_headers);

 vlan_present = MLX5_GET(fte_match_set_lyr_2_4, headers_v, cvlan_tag);
 if (vlan_present) {
  prio_mask = MLX5_GET(fte_match_set_lyr_2_4, headers_c, first_prio);
  prio_val = MLX5_GET(fte_match_set_lyr_2_4, headers_v, first_prio);
 }

 if (!vlan_present || !prio_mask) {
  prio_val = UNKNOWN_MATCH_PRIO;
 } else if (prio_mask != 0x7) {
  NL_SET_ERR_MSG_MOD(extack,
       "masked priority match not supported for hairpin");
  return -EOPNOTSUPP;
 }

 *match_prio = prio_val;
 return 0;
}

static int debugfs_hairpin_num_active_get(void *data, u64 *val)
{
 struct mlx5e_tc_table *tc = data;
 struct mlx5e_hairpin_entry *hpe;
 u32 cnt = 0;
 u32 bkt;

 mutex_lock(&tc->hairpin_tbl_lock);
 hash_for_each(tc->hairpin_tbl, bkt, hpe, hairpin_hlist)
  cnt++;
 mutex_unlock(&tc->hairpin_tbl_lock);

 *val = cnt;

 return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(fops_hairpin_num_active,
    debugfs_hairpin_num_active_get, NULL, "%llu\n");

static int debugfs_hairpin_table_dump_show(struct seq_file *file, void *priv)

{
 struct mlx5e_tc_table *tc = file->private;
 struct mlx5e_hairpin_entry *hpe;
 u32 bkt;

 mutex_lock(&tc->hairpin_tbl_lock);
 hash_for_each(tc->hairpin_tbl, bkt, hpe, hairpin_hlist)
  seq_printf(file,
      "Hairpin peer_vhca_id %u prio %u refcnt %u num_channels %u num_packets %lu\n",
      hpe->peer_vhca_id, hpe->prio,
      refcount_read(&hpe->refcnt), hpe->hp->num_channels,
      BIT(hpe->hp->log_num_packets));
 mutex_unlock(&tc->hairpin_tbl_lock);

 return 0;
}
DEFINE_SHOW_ATTRIBUTE(debugfs_hairpin_table_dump);

static void mlx5e_tc_debugfs_init(struct mlx5e_tc_table *tc,
      struct dentry *dfs_root)
{
 if (IS_ERR_OR_NULL(dfs_root))
  return;

 tc->dfs_root = debugfs_create_dir("tc", dfs_root);

 debugfs_create_file("hairpin_num_active", 0444, tc->dfs_root, tc,
       &fops_hairpin_num_active);
 debugfs_create_file("hairpin_table_dump", 0444, tc->dfs_root, tc,
       &debugfs_hairpin_table_dump_fops);
}

static int mlx5e_hairpin_flow_add(struct mlx5e_priv *priv,
      struct mlx5e_tc_flow *flow,
      struct mlx5e_tc_flow_parse_attr *parse_attr,
      struct netlink_ext_ack *extack)
{
 struct mlx5e_tc_table *tc = mlx5e_fs_get_tc(priv->fs);
 struct devlink *devlink = priv_to_devlink(priv->mdev);
 int peer_ifindex = parse_attr->mirred_ifindex[0];
 union devlink_param_value val = {};
 struct mlx5_hairpin_params params;
 struct mlx5_core_dev *peer_mdev;
 struct mlx5e_hairpin_entry *hpe;
 struct mlx5e_hairpin *hp;
 u8 match_prio;
 u16 peer_id;
 int err;

 peer_mdev = mlx5e_hairpin_get_mdev(dev_net(priv->netdev), peer_ifindex);
 if (IS_ERR(peer_mdev)) {
  NL_SET_ERR_MSG_MOD(extack, "invalid ifindex of mirred device");
  return PTR_ERR(peer_mdev);
 }

 if (!MLX5_CAP_GEN(priv->mdev, hairpin) || !MLX5_CAP_GEN(peer_mdev, hairpin)) {
  NL_SET_ERR_MSG_MOD(extack, "hairpin is not supported");
  return -EOPNOTSUPP;
 }

 peer_id = MLX5_CAP_GEN(peer_mdev, vhca_id);
 err = mlx5e_hairpin_get_prio(priv, &parse_attr->spec, &match_prio,
         extack);
 if (err)
  return err;

 mutex_lock(&tc->hairpin_tbl_lock);
 hpe = mlx5e_hairpin_get(priv, peer_id, match_prio);
 if (hpe) {
  mutex_unlock(&tc->hairpin_tbl_lock);
  wait_for_completion(&hpe->res_ready);

  if (IS_ERR(hpe->hp)) {
   err = -EREMOTEIO;
   goto out_err;
  }
  goto attach_flow;
 }

 hpe = kzalloc(sizeof(*hpe), GFP_KERNEL);
 if (!hpe) {
  mutex_unlock(&tc->hairpin_tbl_lock);
  return -ENOMEM;
 }

 spin_lock_init(&hpe->flows_lock);
 INIT_LIST_HEAD(&hpe->flows);
 INIT_LIST_HEAD(&hpe->dead_peer_wait_list);
 hpe->peer_vhca_id = peer_id;
 hpe->prio = match_prio;
 refcount_set(&hpe->refcnt, 1);
 init_completion(&hpe->res_ready);

 hash_add(tc->hairpin_tbl, &hpe->hairpin_hlist,
   hash_hairpin_info(peer_id, match_prio));
 mutex_unlock(&tc->hairpin_tbl_lock);

 err = devl_param_driverinit_value_get(
  devlink, MLX5_DEVLINK_PARAM_ID_HAIRPIN_QUEUE_SIZE, &val);
 if (err) {
  err = -ENOMEM;
  goto out_err;
 }

 params.log_num_packets = ilog2(val.vu32);
 params.log_data_size =
  clamp_t(u32,
   params.log_num_packets +
    MLX5_MPWRQ_MIN_LOG_STRIDE_SZ(priv->mdev),
   MLX5_CAP_GEN(priv->mdev, log_min_hairpin_wq_data_sz),
   MLX5_CAP_GEN(priv->mdev, log_max_hairpin_wq_data_sz));

 params.q_counter = priv->q_counter[0];
 err = devl_param_driverinit_value_get(
  devlink, MLX5_DEVLINK_PARAM_ID_HAIRPIN_NUM_QUEUES, &val);
 if (err) {
  err = -ENOMEM;
  goto out_err;
 }

 params.num_channels = val.vu32;

 hp = mlx5e_hairpin_create(priv, ¶ms, peer_ifindex);
 hpe->hp = hp;
 complete_all(&hpe->res_ready);
 if (IS_ERR(hp)) {
  err = PTR_ERR(hp);
  goto out_err;
 }

 netdev_dbg(priv->netdev, "add hairpin: tirn %x rqn %x peer %s sqn %x prio %d (log) data %d packets %d\n",
     mlx5e_tir_get_tirn(&hp->direct_tir), hp->pair->rqn[0],
     dev_name(hp->pair->peer_mdev->device),
     hp->pair->sqn[0], match_prio, params.log_data_size, params.log_num_packets);

attach_flow:
 if (hpe->hp->num_channels > 1) {
  flow_flag_set(flow, HAIRPIN_RSS);
  flow->attr->nic_attr->hairpin_ft =
   mlx5_get_ttc_flow_table(hpe->hp->ttc);
 } else {
  flow->attr->nic_attr->hairpin_tirn = mlx5e_tir_get_tirn(&hpe->hp->direct_tir);
 }

 flow->hpe = hpe;
 spin_lock(&hpe->flows_lock);
 list_add(&flow->hairpin, &hpe->flows);
 spin_unlock(&hpe->flows_lock);

 return 0;

out_err:
 mlx5e_hairpin_put(priv, hpe);
 return err;
}

static void mlx5e_hairpin_flow_del(struct mlx5e_priv *priv,
       struct mlx5e_tc_flow *flow)
{
 /* flow wasn't fully initialized */
 if (!flow->hpe)
  return;

 spin_lock(&flow->hpe->flows_lock);
 list_del(&flow->hairpin);
 spin_unlock(&flow->hpe->flows_lock);

 mlx5e_hairpin_put(priv, flow->hpe);
 flow->hpe = NULL;
}

struct mlx5_flow_handle *
mlx5e_add_offloaded_nic_rule(struct mlx5e_priv *priv,
        struct mlx5_flow_spec *spec,
        struct mlx5_flow_attr *attr)
{
 struct mlx5_flow_context *flow_context = &spec->flow_context;
 struct mlx5e_vlan_table *vlan = mlx5e_fs_get_vlan(priv->fs);
 struct mlx5e_tc_table *tc = mlx5e_fs_get_tc(priv->fs);
 struct mlx5_nic_flow_attr *nic_attr = attr->nic_attr;
 struct mlx5_flow_destination dest[2] = {};
 struct mlx5_fs_chains *nic_chains;
 struct mlx5_flow_act flow_act = {
  .action = attr->action,
  .flags    = FLOW_ACT_NO_APPEND,
 };
 struct mlx5_flow_handle *rule;
 struct mlx5_flow_table *ft;
 int dest_ix = 0;

 nic_chains = mlx5e_nic_chains(tc);
 flow_context->flags |= FLOW_CONTEXT_HAS_TAG;
 flow_context->flow_tag = nic_attr->flow_tag;

 if (attr->dest_ft) {
  dest[dest_ix].type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE;
  dest[dest_ix].ft = attr->dest_ft;
  dest_ix++;
 } else if (nic_attr->hairpin_ft) {
  dest[dest_ix].type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE;
  dest[dest_ix].ft = nic_attr->hairpin_ft;
  dest_ix++;
 } else if (nic_attr->hairpin_tirn) {
  dest[dest_ix].type = MLX5_FLOW_DESTINATION_TYPE_TIR;
  dest[dest_ix].tir_num = nic_attr->hairpin_tirn;
  dest_ix++;
 } else if (attr->action & MLX5_FLOW_CONTEXT_ACTION_FWD_DEST) {
  dest[dest_ix].type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE;
  if (attr->dest_chain) {
   dest[dest_ix].ft = mlx5_chains_get_table(nic_chains,
         attr->dest_chain, 1,
         MLX5E_TC_FT_LEVEL);
   if (IS_ERR(dest[dest_ix].ft))
    return ERR_CAST(dest[dest_ix].ft);
  } else {
   dest[dest_ix].ft = mlx5e_vlan_get_flowtable(vlan);
  }
  dest_ix++;
 }

 if (dest[0].type == MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE &&
     MLX5_CAP_FLOWTABLE_NIC_RX(priv->mdev, ignore_flow_level))
  flow_act.flags |= FLOW_ACT_IGNORE_FLOW_LEVEL;

 if (flow_act.action & MLX5_FLOW_CONTEXT_ACTION_COUNT) {
  dest[dest_ix].type = MLX5_FLOW_DESTINATION_TYPE_COUNTER;
  dest[dest_ix].counter = attr->counter;
  dest_ix++;
 }

 if (attr->action & MLX5_FLOW_CONTEXT_ACTION_MOD_HDR)
  flow_act.modify_hdr = attr->modify_hdr;

 mutex_lock(&tc->t_lock);
 if (IS_ERR_OR_NULL(tc->t)) {
  /* Create the root table here if doesn't exist yet */
  tc->t =
   mlx5_chains_get_table(nic_chains, 0, 1, MLX5E_TC_FT_LEVEL);

  if (IS_ERR(tc->t)) {
   mutex_unlock(&tc->t_lock);
   netdev_err(priv->netdev,
       "Failed to create tc offload table\n");
   rule = ERR_CAST(tc->t);
   goto err_ft_get;
  }
 }
 mutex_unlock(&tc->t_lock);

 if (attr->chain || attr->prio)
  ft = mlx5_chains_get_table(nic_chains,
        attr->chain, attr->prio,
        MLX5E_TC_FT_LEVEL);
 else
  ft = attr->ft;

 if (IS_ERR(ft)) {
  rule = ERR_CAST(ft);
  goto err_ft_get;
 }

 if (attr->outer_match_level != MLX5_MATCH_NONE)
  spec->match_criteria_enable |= MLX5_MATCH_OUTER_HEADERS;

 rule = mlx5_add_flow_rules(ft, spec,
       &flow_act, dest, dest_ix);
 if (IS_ERR(rule))
  goto err_rule;

 return rule;

err_rule:
 if (attr->chain || attr->prio)
  mlx5_chains_put_table(nic_chains,
          attr->chain, attr->prio,
          MLX5E_TC_FT_LEVEL);
err_ft_get:
 if (attr->dest_chain)
  mlx5_chains_put_table(nic_chains,
          attr->dest_chain, 1,
          MLX5E_TC_FT_LEVEL);

 return ERR_CAST(rule);
}

static int
alloc_flow_attr_counter(struct mlx5_core_dev *counter_dev,
   struct mlx5_flow_attr *attr)

{
 struct mlx5_fc *counter;

 counter = mlx5_fc_create(counter_dev, true);
 if (IS_ERR(counter))
  return PTR_ERR(counter);

 attr->counter = counter;
 return 0;
}

static int
mlx5e_tc_add_nic_flow(struct mlx5e_priv *priv,
        struct mlx5e_tc_flow *flow,
        struct netlink_ext_ack *extack)
{
 struct mlx5e_tc_flow_parse_attr *parse_attr;
 struct mlx5_flow_attr *attr = flow->attr;
 struct mlx5_core_dev *dev = priv->mdev;
 int err;

 parse_attr = attr->parse_attr;

 if (flow_flag_test(flow, HAIRPIN)) {
  err = mlx5e_hairpin_flow_add(priv, flow, parse_attr, extack);
  if (err)
   return err;
 }

 if (attr->action & MLX5_FLOW_CONTEXT_ACTION_COUNT) {
  err = alloc_flow_attr_counter(dev, attr);
  if (err)
   return err;
 }

 if (attr->action & MLX5_FLOW_CONTEXT_ACTION_MOD_HDR) {
  err = mlx5e_tc_attach_mod_hdr(priv, flow, attr);
  if (err)
   return err;
 }

 flow->rule[0] = mlx5e_add_offloaded_nic_rule(priv, &parse_attr->spec, attr);
 return PTR_ERR_OR_ZERO(flow->rule[0]);
}

void mlx5e_del_offloaded_nic_rule(struct mlx5e_priv *priv,
      struct mlx5_flow_handle *rule,
      struct mlx5_flow_attr *attr)
{
 struct mlx5e_tc_table *tc = mlx5e_fs_get_tc(priv->fs);
 struct mlx5_fs_chains *nic_chains;

 nic_chains = mlx5e_nic_chains(tc);
 mlx5_del_flow_rules(rule);

 if (attr->chain || attr->prio)
  mlx5_chains_put_table(nic_chains, attr->chain, attr->prio,
          MLX5E_TC_FT_LEVEL);

 if (attr->dest_chain)
  mlx5_chains_put_table(nic_chains, attr->dest_chain, 1,
          MLX5E_TC_FT_LEVEL);
}

static void mlx5e_tc_del_nic_flow(struct mlx5e_priv *priv,
      struct mlx5e_tc_flow *flow)
{
 struct mlx5e_tc_table *tc = mlx5e_fs_get_tc(priv->fs);
 struct mlx5_flow_attr *attr = flow->attr;

 flow_flag_clear(flow, OFFLOADED);

 if (!IS_ERR_OR_NULL(flow->rule[0]))
  mlx5e_del_offloaded_nic_rule(priv, flow->rule[0], attr);

 /* Remove root table if no rules are left to avoid
 * extra steering hops.
 */

 mutex_lock(&tc->t_lock);
 if (!mlx5e_tc_num_filters(priv, MLX5_TC_FLAG(NIC_OFFLOAD)) &&
     !IS_ERR_OR_NULL(tc->t)) {
  mlx5_chains_put_table(mlx5e_nic_chains(tc), 0, 1, MLX5E_TC_FT_LEVEL);
  tc->t = NULL;
 }
 mutex_unlock(&tc->t_lock);

 if (attr->action & MLX5_FLOW_CONTEXT_ACTION_MOD_HDR) {
  mlx5e_mod_hdr_dealloc(&attr->parse_attr->mod_hdr_acts);
  mlx5e_tc_detach_mod_hdr(priv, flow, attr);
 }

 if (attr->action & MLX5_FLOW_CONTEXT_ACTION_COUNT)
  mlx5_fc_destroy(priv->mdev, attr->counter);

 if (flow_flag_test(flow, HAIRPIN))
  mlx5e_hairpin_flow_del(priv, flow);

 free_flow_post_acts(flow);
 mlx5_tc_ct_delete_flow(get_ct_priv(flow->priv), attr);

 kvfree(attr->parse_attr);
 kfree(flow->attr);
}

struct mlx5_flow_handle *
mlx5e_tc_offload_fdb_rules(struct mlx5_eswitch *esw,
      struct mlx5e_tc_flow *flow,
      struct mlx5_flow_spec *spec,
      struct mlx5_flow_attr *attr)
{
 struct mlx5_flow_handle *rule;

 if (attr->flags & MLX5_ATTR_FLAG_SLOW_PATH)
  return mlx5_eswitch_add_offloaded_rule(esw, spec, attr);

 rule = mlx5e_tc_rule_offload(flow->priv, spec, attr);

 if (IS_ERR(rule))
  return rule;

 if (attr->esw_attr->split_count) {
  flow->rule[1] = mlx5_eswitch_add_fwd_rule(esw, spec, attr);
  if (IS_ERR(flow->rule[1]))
   goto err_rule1;
 }

 return rule;

err_rule1:
 mlx5e_tc_rule_unoffload(flow->priv, rule, attr);
 return flow->rule[1];
}

void mlx5e_tc_unoffload_fdb_rules(struct mlx5_eswitch *esw,
      struct mlx5e_tc_flow *flow,
      struct mlx5_flow_attr *attr)
{
 flow_flag_clear(flow, OFFLOADED);

 if (attr->flags & MLX5_ATTR_FLAG_SLOW_PATH)
  return mlx5_eswitch_del_offloaded_rule(esw, flow->rule[0], attr);

 if (attr->esw_attr->split_count)
  mlx5_eswitch_del_fwd_rule(esw, flow->rule[1], attr);

 mlx5e_tc_rule_unoffload(flow->priv, flow->rule[0], attr);
}

struct mlx5_flow_handle *
mlx5e_tc_offload_to_slow_path(struct mlx5_eswitch *esw,
         struct mlx5e_tc_flow *flow,
         struct mlx5_flow_spec *spec)
{
 struct mlx5e_tc_mod_hdr_acts mod_acts = {};
 struct mlx5e_mod_hdr_handle *mh = NULL;
 struct mlx5_flow_attr *slow_attr;
 struct mlx5_flow_handle *rule;
 bool fwd_and_modify_cap;
 u32 chain_mapping = 0;
 int err;

 slow_attr = mlx5_alloc_flow_attr(MLX5_FLOW_NAMESPACE_FDB);
 if (!slow_attr)
  return ERR_PTR(-ENOMEM);

 memcpy(slow_attr, flow->attr, ESW_FLOW_ATTR_SZ);
 slow_attr->action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST;
 slow_attr->esw_attr->split_count = 0;
 slow_attr->flags |= MLX5_ATTR_FLAG_SLOW_PATH;

 fwd_and_modify_cap = MLX5_CAP_ESW_FLOWTABLE((esw)->dev, fdb_modify_header_fwd_to_table);
 if (!fwd_and_modify_cap)
  goto skip_restore;

 err = mlx5_chains_get_chain_mapping(esw_chains(esw), flow->attr->chain, &chain_mapping);
 if (err)
  goto err_get_chain;

 err = mlx5e_tc_match_to_reg_set(esw->dev, &mod_acts, MLX5_FLOW_NAMESPACE_FDB,
     MAPPED_OBJ_TO_REG, chain_mapping);
 if (err)
  goto err_reg_set;

 mh = mlx5e_mod_hdr_attach(esw->dev, get_mod_hdr_table(flow->priv, flow),
      MLX5_FLOW_NAMESPACE_FDB, &mod_acts);
 if (IS_ERR(mh)) {
  err = PTR_ERR(mh);
  goto err_attach;
 }

 slow_attr->action |= MLX5_FLOW_CONTEXT_ACTION_MOD_HDR;
 slow_attr->modify_hdr = mlx5e_mod_hdr_get(mh);

skip_restore:
 rule = mlx5e_tc_offload_fdb_rules(esw, flow, spec, slow_attr);
 if (IS_ERR(rule)) {
  err = PTR_ERR(rule);
  goto err_offload;
 }

 flow->attr->slow_mh = mh;
 flow->chain_mapping = chain_mapping;
 flow_flag_set(flow, SLOW);

 mlx5e_mod_hdr_dealloc(&mod_acts);
 kfree(slow_attr);

 return rule;

err_offload:
 if (fwd_and_modify_cap)
  mlx5e_mod_hdr_detach(esw->dev, get_mod_hdr_table(flow->priv, flow), mh);
err_attach:
err_reg_set:
 if (fwd_and_modify_cap)
  mlx5_chains_put_chain_mapping(esw_chains(esw), chain_mapping);
err_get_chain:
 mlx5e_mod_hdr_dealloc(&mod_acts);
 kfree(slow_attr);
 return ERR_PTR(err);
}

void mlx5e_tc_unoffload_from_slow_path(struct mlx5_eswitch *esw,
           struct mlx5e_tc_flow *flow)
{
 struct mlx5e_mod_hdr_handle *slow_mh = flow->attr->slow_mh;
 struct mlx5_flow_attr *slow_attr;

 slow_attr = mlx5_alloc_flow_attr(MLX5_FLOW_NAMESPACE_FDB);
 if (!slow_attr) {
  mlx5_core_warn(flow->priv->mdev, "Unable to alloc attr to unoffload slow path rule\n");
  return;
 }

 memcpy(slow_attr, flow->attr, ESW_FLOW_ATTR_SZ);
 slow_attr->action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST;
 slow_attr->esw_attr->split_count = 0;
 slow_attr->flags |= MLX5_ATTR_FLAG_SLOW_PATH;
 if (slow_mh) {
  slow_attr->action |= MLX5_FLOW_CONTEXT_ACTION_MOD_HDR;
  slow_attr->modify_hdr = mlx5e_mod_hdr_get(slow_mh);
 }
 mlx5e_tc_unoffload_fdb_rules(esw, flow, slow_attr);
 if (slow_mh) {
  mlx5e_mod_hdr_detach(esw->dev, get_mod_hdr_table(flow->priv, flow), slow_mh);
  mlx5_chains_put_chain_mapping(esw_chains(esw), flow->chain_mapping);
  flow->chain_mapping = 0;
  flow->attr->slow_mh = NULL;
 }
 flow_flag_clear(flow, SLOW);
 kfree(slow_attr);
}

/* Caller must obtain uplink_priv->unready_flows_lock mutex before calling this
 * function.
 */

static void unready_flow_add(struct mlx5e_tc_flow *flow,
        struct list_head *unready_flows)
{
 flow_flag_set(flow, NOT_READY);
 list_add_tail(&flow->unready, unready_flows);
}

/* Caller must obtain uplink_priv->unready_flows_lock mutex before calling this
 * function.
 */

static void unready_flow_del(struct mlx5e_tc_flow *flow)
{
 list_del(&flow->unready);
 flow_flag_clear(flow, NOT_READY);
}

static void add_unready_flow(struct mlx5e_tc_flow *flow)
{
 struct mlx5_rep_uplink_priv *uplink_priv;
 struct mlx5e_rep_priv *rpriv;
 struct mlx5_eswitch *esw;

 esw = flow->priv->mdev->priv.eswitch;
 rpriv = mlx5_eswitch_get_uplink_priv(esw, REP_ETH);
 uplink_priv = &rpriv->uplink_priv;

 mutex_lock(&uplink_priv->unready_flows_lock);
 unready_flow_add(flow, &uplink_priv->unready_flows);
 mutex_unlock(&uplink_priv->unready_flows_lock);
}

static void remove_unready_flow(struct mlx5e_tc_flow *flow)
{
 struct mlx5_rep_uplink_priv *uplink_priv;
 struct mlx5e_rep_priv *rpriv;
 struct mlx5_eswitch *esw;

 esw = flow->priv->mdev->priv.eswitch;
 rpriv = mlx5_eswitch_get_uplink_priv(esw, REP_ETH);
 uplink_priv = &rpriv->uplink_priv;

 mutex_lock(&uplink_priv->unready_flows_lock);
 if (flow_flag_test(flow, NOT_READY))
  unready_flow_del(flow);
 mutex_unlock(&uplink_priv->unready_flows_lock);
}

bool mlx5e_tc_is_vf_tunnel(struct net_device *out_dev, struct net_device *route_dev)
{
 struct mlx5_core_dev *out_mdev, *route_mdev;
 struct mlx5e_priv *out_priv, *route_priv;

 out_priv = netdev_priv(out_dev);
 out_mdev = out_priv->mdev;
 route_priv = netdev_priv(route_dev);
 route_mdev = route_priv->mdev;

 if (out_mdev->coredev_type != MLX5_COREDEV_PF)
  return false;

 if (route_mdev->coredev_type != MLX5_COREDEV_VF &&
     route_mdev->coredev_type != MLX5_COREDEV_SF)
  return false;

 return mlx5e_same_hw_devs(out_priv, route_priv);
}

int mlx5e_tc_query_route_vport(struct net_device *out_dev, struct net_device *route_dev, u16 *vport)
{
 struct mlx5e_priv *out_priv, *route_priv;
 struct mlx5_core_dev *route_mdev;
 struct mlx5_devcom_comp_dev *pos;
 struct mlx5_eswitch *esw;
 u16 vhca_id;
 int err;

 out_priv = netdev_priv(out_dev);
 esw = out_priv->mdev->priv.eswitch;
 route_priv = netdev_priv(route_dev);
 route_mdev = route_priv->mdev;

 vhca_id = MLX5_CAP_GEN(route_mdev, vhca_id);
 err = mlx5_eswitch_vhca_id_to_vport(esw, vhca_id, vport);
 if (!err)
  return err;

 if (!mlx5_lag_is_active(out_priv->mdev))
  return err;

 rcu_read_lock();
 err = -ENODEV;
 mlx5_devcom_for_each_peer_entry_rcu(esw->devcom, esw, pos) {
  err = mlx5_eswitch_vhca_id_to_vport(esw, vhca_id, vport);
  if (!err)
   break;
 }
 rcu_read_unlock();

 return err;
}

static int
verify_attr_actions(u32 actions, struct netlink_ext_ack *extack)
{
 if (!(actions &
       (MLX5_FLOW_CONTEXT_ACTION_FWD_DEST | MLX5_FLOW_CONTEXT_ACTION_DROP))) {
  NL_SET_ERR_MSG_MOD(extack, "Rule must have at least one forward/drop action");
  return -EOPNOTSUPP;
 }

 if (!(~actions &
       (MLX5_FLOW_CONTEXT_ACTION_FWD_DEST | MLX5_FLOW_CONTEXT_ACTION_DROP))) {
  NL_SET_ERR_MSG_MOD(extack, "Rule cannot support forward+drop action");
  return -EOPNOTSUPP;
 }

 if (actions & MLX5_FLOW_CONTEXT_ACTION_MOD_HDR &&
     actions & MLX5_FLOW_CONTEXT_ACTION_DROP) {
  NL_SET_ERR_MSG_MOD(extack, "Drop with modify header action is not supported");
  return -EOPNOTSUPP;
 }

 return 0;
}

static bool
has_encap_dests(struct mlx5_flow_attr *attr)
{
 struct mlx5_esw_flow_attr *esw_attr = attr->esw_attr;
 int out_index;

 for (out_index = 0; out_index < MLX5_MAX_FLOW_FWD_VPORTS; out_index++)
  if (esw_attr->dests[out_index].flags & MLX5_ESW_DEST_ENCAP)
   return true;

 return false;
}

static int
extra_split_attr_dests_needed(struct mlx5e_tc_flow *flow, struct mlx5_flow_attr *attr)
{
 bool int_dest = false, ext_dest = false;
 struct mlx5_esw_flow_attr *esw_attr;
 int i;

 if (flow->attr != attr ||
     !list_is_first(&attr->list, &flow->attrs))
  return 0;

 esw_attr = attr->esw_attr;
 if (!esw_attr->split_count ||
     esw_attr->split_count == esw_attr->out_count - 1)
  return 0;

 if (esw_attr->dest_int_port &&
     (esw_attr->dests[esw_attr->split_count].flags &
      MLX5_ESW_DEST_CHAIN_WITH_SRC_PORT_CHANGE))
  return esw_attr->split_count + 1;

 for (i = esw_attr->split_count; i < esw_attr->out_count; i++) {
  /* external dest with encap is considered as internal by firmware */
  if (esw_attr->dests[i].vport == MLX5_VPORT_UPLINK &&
      !(esw_attr->dests[i].flags & MLX5_ESW_DEST_ENCAP))
   ext_dest = true;
  else
   int_dest = true;

  if (ext_dest && int_dest)
   return esw_attr->split_count;
 }

 return 0;
}

static int
extra_split_attr_dests(struct mlx5e_tc_flow *flow,
         struct mlx5_flow_attr *attr, int split_count)
{
 struct mlx5e_post_act *post_act = get_post_action(flow->priv);
 struct mlx5e_tc_flow_parse_attr *parse_attr, *parse_attr2;
 struct mlx5_esw_flow_attr *esw_attr, *esw_attr2;
 struct mlx5e_post_act_handle *handle;
 struct mlx5_flow_attr *attr2;
 int i, j, err;

 if (IS_ERR(post_act))
  return PTR_ERR(post_act);

 attr2 = mlx5_alloc_flow_attr(mlx5e_get_flow_namespace(flow));
 parse_attr2 = kvzalloc(sizeof(*parse_attr), GFP_KERNEL);
 if (!attr2 || !parse_attr2) {
  err = -ENOMEM;
  goto err_free;
 }
 attr2->parse_attr = parse_attr2;

 handle = mlx5e_tc_post_act_add(post_act, attr2);
 if (IS_ERR(handle)) {
  err = PTR_ERR(handle);
  goto err_free;
 }

 esw_attr = attr->esw_attr;
 esw_attr2 = attr2->esw_attr;
 esw_attr2->in_rep = esw_attr->in_rep;

 parse_attr = attr->parse_attr;
 parse_attr2->filter_dev = parse_attr->filter_dev;

 for (i = split_count, j = 0; i < esw_attr->out_count; i++, j++)
  esw_attr2->dests[j] = esw_attr->dests[i];

 esw_attr2->out_count = j;
 attr2->action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST;

 err = mlx5e_tc_post_act_offload(post_act, handle);
 if (err)
  goto err_post_act_offload;

 err = mlx5e_tc_post_act_set_handle(flow->priv->mdev, handle,
        &parse_attr->mod_hdr_acts);
 if (err)
  goto err_post_act_set_handle;

 esw_attr->out_count = split_count;
 attr->extra_split_ft = mlx5e_tc_post_act_get_ft(post_act);
 flow->extra_split_attr = attr2;

 attr2->post_act_handle = handle;

 return 0;

err_post_act_set_handle:
 mlx5e_tc_post_act_unoffload(post_act, handle);
err_post_act_offload:
 mlx5e_tc_post_act_del(post_act, handle);
err_free:
 kvfree(parse_attr2);
 kfree(attr2);
 return err;
}

static int
post_process_attr(struct mlx5e_tc_flow *flow,
    struct mlx5_flow_attr *attr,
    struct netlink_ext_ack *extack)
{
 int extra_split;
 bool vf_tun;
 int err = 0;

 err = verify_attr_actions(attr->action, extack);
 if (err)
  goto err_out;

 if (mlx5e_is_eswitch_flow(flow) && has_encap_dests(attr)) {
  err = mlx5e_tc_tun_encap_dests_set(flow->priv, flow, attr, extack, &vf_tun);
  if (err)
   goto err_out;
 }

 extra_split = extra_split_attr_dests_needed(flow, attr);
 if (extra_split > 0) {
  err = extra_split_attr_dests(flow, attr, extra_split);
  if (err)
   goto err_out;
 }

 if (attr->action & MLX5_FLOW_CONTEXT_ACTION_MOD_HDR) {
  err = mlx5e_tc_attach_mod_hdr(flow->priv, flow, attr);
  if (err)
   goto err_out;
 }

 if (attr->branch_true &&
     attr->branch_true->action & MLX5_FLOW_CONTEXT_ACTION_MOD_HDR) {
  err = mlx5e_tc_attach_mod_hdr(flow->priv, flow, attr->branch_true);
  if (err)
   goto err_out;
 }

 if (attr->branch_false &&
     attr->branch_false->action & MLX5_FLOW_CONTEXT_ACTION_MOD_HDR) {
  err = mlx5e_tc_attach_mod_hdr(flow->priv, flow, attr->branch_false);
  if (err)
   goto err_out;
 }

 if (attr->action & MLX5_FLOW_CONTEXT_ACTION_COUNT) {
  err = alloc_flow_attr_counter(get_flow_counter_dev(flow), attr);
  if (err)
   goto err_out;
 }

err_out:
 return err;
}

static int
mlx5e_tc_add_fdb_flow(struct mlx5e_priv *priv,
        struct mlx5e_tc_flow *flow,
        struct netlink_ext_ack *extack)
{
 struct mlx5_eswitch *esw = priv->mdev->priv.eswitch;
 struct mlx5e_tc_flow_parse_attr *parse_attr;
 struct mlx5_flow_attr *attr = flow->attr;
 struct mlx5_esw_flow_attr *esw_attr;
 u32 max_prio, max_chain;
 int err = 0;

 parse_attr = attr->parse_attr;
 esw_attr = attr->esw_attr;

 /* We check chain range only for tc flows.
 * For ft flows, we checked attr->chain was originally 0 and set it to
 * FDB_FT_CHAIN which is outside tc range.
 * See mlx5e_rep_setup_ft_cb().
 */

 max_chain = mlx5_chains_get_chain_range(esw_chains(esw));
 if (!mlx5e_is_ft_flow(flow) && attr->chain > max_chain) {
  NL_SET_ERR_MSG_MOD(extack,
       "Requested chain is out of supported range");
  err = -EOPNOTSUPP;
  goto err_out;
 }

 max_prio = mlx5_chains_get_prio_range(esw_chains(esw));
 if (attr->prio > max_prio) {
  NL_SET_ERR_MSG_MOD(extack,
       "Requested priority is out of supported range");
  err = -EOPNOTSUPP;
  goto err_out;
 }

 if (flow_flag_test(flow, TUN_RX)) {
  err = mlx5e_attach_decap_route(priv, flow);
  if (err)
   goto err_out;

  if (!attr->chain && esw_attr->int_port &&
      attr->action & MLX5_FLOW_CONTEXT_ACTION_FWD_DEST) {
   /* If decap route device is internal port, change the
 * source vport value in reg_c0 back to uplink just in
 * case the rule performs goto chain > 0. If we have a miss
 * on chain > 0 we want the metadata regs to hold the
 * chain id so SW will resume handling of this packet
 * from the proper chain.
 */

   u32 metadata = mlx5_eswitch_get_vport_metadata_for_set(esw,
         esw_attr->in_rep->vport);

   err = mlx5e_tc_match_to_reg_set(priv->mdev, &parse_attr->mod_hdr_acts,
       MLX5_FLOW_NAMESPACE_FDB, VPORT_TO_REG,
       metadata);
   if (err)
    goto err_out;

   attr->action |= MLX5_FLOW_CONTEXT_ACTION_MOD_HDR;
  }
 }

 if (flow_flag_test(flow, L3_TO_L2_DECAP)) {
  err = mlx5e_attach_decap(priv, flow, extack);
  if (err)
   goto err_out;
 }

 if (netif_is_ovs_master(parse_attr->filter_dev)) {
  struct mlx5e_tc_int_port *int_port;

  if (attr->chain) {
   NL_SET_ERR_MSG_MOD(extack,
        "Internal port rule is only supported on chain 0");
   err = -EOPNOTSUPP;
   goto err_out;
  }

  if (attr->dest_chain) {
   NL_SET_ERR_MSG_MOD(extack,
        "Internal port rule offload doesn't support goto action");
   err = -EOPNOTSUPP;
   goto err_out;
  }

  int_port = mlx5e_tc_int_port_get(mlx5e_get_int_port_priv(priv),
       parse_attr->filter_dev->ifindex,
       flow_flag_test(flow, EGRESS) ?
       MLX5E_TC_INT_PORT_EGRESS :
       MLX5E_TC_INT_PORT_INGRESS);
  if (IS_ERR(int_port)) {
   err = PTR_ERR(int_port);
   goto err_out;
  }

  esw_attr->int_port = int_port;
 }

 err = post_process_attr(flow, attr, extack);
 if (err)
  goto err_out;

 err = mlx5e_tc_act_stats_add_flow(get_act_stats_handle(priv), flow);
 if (err)
  goto err_out;

 /* we get here if one of the following takes place:
 * (1) there's no error
 * (2) there's an encap action and we don't have valid neigh
 */

 if (flow_flag_test(flow, SLOW))
  flow->rule[0] = mlx5e_tc_offload_to_slow_path(esw, flow, &parse_attr->spec);
 else
  flow->rule[0] = mlx5e_tc_offload_fdb_rules(esw, flow, &parse_attr->spec, attr);

 if (IS_ERR(flow->rule[0])) {
  err = PTR_ERR(flow->rule[0]);
  goto err_out;
 }
 flow_flag_set(flow, OFFLOADED);

 return 0;

err_out:
 flow_flag_set(flow, FAILED);
 return err;
}

static bool mlx5_flow_has_geneve_opt(struct mlx5_flow_spec *spec)
{
 void *headers_v = MLX5_ADDR_OF(fte_match_param,
           spec->match_value,
           misc_parameters_3);
 u32 geneve_tlv_opt_0_data = MLX5_GET(fte_match_set_misc3,
          headers_v,
          geneve_tlv_option_0_data);

 return !!geneve_tlv_opt_0_data;
}

static void free_branch_attr(struct mlx5e_tc_flow *flow, struct mlx5_flow_attr *attr)
{
 if (!attr)
  return;

 mlx5_free_flow_attr_actions(flow, attr);
 kvfree(attr->parse_attr);
 kfree(attr);
}

static void mlx5e_tc_del_fdb_flow(struct mlx5e_priv *priv,
      struct mlx5e_tc_flow *flow)
{
 struct mlx5_eswitch *esw = priv->mdev->priv.eswitch;
 struct mlx5_flow_attr *attr = flow->attr;

 mlx5e_put_flow_tunnel_id(flow);

 remove_unready_flow(flow);

 if (mlx5e_is_offloaded_flow(flow)) {
  if (flow_flag_test(flow, SLOW))
   mlx5e_tc_unoffload_from_slow_path(esw, flow);
  else
   mlx5e_tc_unoffload_fdb_rules(esw, flow, attr);
 }
 complete_all(&flow->del_hw_done);

 if (mlx5_flow_has_geneve_opt(&attr->parse_attr->spec))
  mlx5_geneve_tlv_option_del(priv->mdev->geneve);

 if (flow->decap_route)
  mlx5e_detach_decap_route(priv, flow);

 mlx5_tc_ct_match_del(get_ct_priv(priv), &flow->attr->ct_attr);

 if (flow_flag_test(flow, L3_TO_L2_DECAP))
  mlx5e_detach_decap(priv, flow);

 mlx5e_tc_act_stats_del_flow(get_act_stats_handle(priv), flow);

 free_flow_post_acts(flow);
 if (flow->extra_split_attr) {
  mlx5_free_flow_attr_actions(flow, flow->extra_split_attr);
  kvfree(flow->extra_split_attr->parse_attr);
  kfree(flow->extra_split_attr);
 }
 mlx5_free_flow_attr_actions(flow, attr);

 kvfree(attr->esw_attr->rx_tun_attr);
 kvfree(attr->parse_attr);
 kfree(flow->attr);
}

struct mlx5_fc *mlx5e_tc_get_counter(struct mlx5e_tc_flow *flow)
{
 struct mlx5_flow_attr *attr;

 attr = list_first_entry(&flow->attrs, struct mlx5_flow_attr, list);
 return attr->counter;
}

/* Iterate over tmp_list of flows attached to flow_list head. */
void mlx5e_put_flow_list(struct mlx5e_priv *priv, struct list_head *flow_list)
{
 struct mlx5e_tc_flow *flow, *tmp;

 list_for_each_entry_safe(flow, tmp, flow_list, tmp_list)
  mlx5e_flow_put(priv, flow);
}

static void mlx5e_tc_del_fdb_peer_flow(struct mlx5e_tc_flow *flow,
           int peer_index)
{
 struct mlx5_eswitch *esw = flow->priv->mdev->priv.eswitch;
 struct mlx5e_tc_flow *peer_flow;
 struct mlx5e_tc_flow *tmp;

 if (!flow_flag_test(flow, ESWITCH) ||
     !flow_flag_test(flow, DUP))
  return;

 mutex_lock(&esw->offloads.peer_mutex);
 list_del(&flow->peer[peer_index]);
 mutex_unlock(&esw->offloads.peer_mutex);

 list_for_each_entry_safe(peer_flow, tmp, &flow->peer_flows, peer_flows) {
  if (peer_index != mlx5_get_dev_index(peer_flow->priv->mdev))
   continue;

  list_del(&peer_flow->peer_flows);
  if (refcount_dec_and_test(&peer_flow->refcnt)) {
   mlx5e_tc_del_fdb_flow(peer_flow->priv, peer_flow);
   kfree(peer_flow);
  }
 }

 if (list_empty(&flow->peer_flows))
  flow_flag_clear(flow, DUP);
}

static void mlx5e_tc_del_fdb_peers_flow(struct mlx5e_tc_flow *flow)
{
 int i;

 for (i = 0; i < MLX5_MAX_PORTS; i++) {
  if (i == mlx5_get_dev_index(flow->priv->mdev))
   continue;
  mlx5e_tc_del_fdb_peer_flow(flow, i);
 }
}

static void mlx5e_tc_del_flow(struct mlx5e_priv *priv,
         struct mlx5e_tc_flow *flow)
{
 if (mlx5e_is_eswitch_flow(flow)) {
  struct mlx5_devcom_comp_dev *devcom = flow->priv->mdev->priv.eswitch->devcom;

  if (!mlx5_devcom_for_each_peer_begin(devcom)) {
   mlx5e_tc_del_fdb_flow(priv, flow);
   return;
  }

  mlx5e_tc_del_fdb_peers_flow(flow);
  mlx5_devcom_for_each_peer_end(devcom);
  mlx5e_tc_del_fdb_flow(priv, flow);
 } else {
  mlx5e_tc_del_nic_flow(priv, flow);
 }
}

static bool flow_requires_tunnel_mapping(u32 chain, struct flow_cls_offload *f)
{
 struct flow_rule *rule = flow_cls_offload_flow_rule(f);
 struct flow_action *flow_action = &rule->action;
 const struct flow_action_entry *act;
 int i;

 if (chain)
  return false;

 flow_action_for_each(i, act, flow_action) {
  switch (act->id) {
  case FLOW_ACTION_GOTO:
   return true;
  case FLOW_ACTION_SAMPLE:
   return true;
  default:
   continue;
  }
 }

 return false;
}

static int
enc_opts_is_dont_care_or_full_match(struct mlx5e_priv *priv,
        struct flow_dissector_key_enc_opts *opts,
        struct netlink_ext_ack *extack,
        bool *dont_care)
{
 struct geneve_opt *opt;
 int off = 0;

 *dont_care = true;

 while (opts->len > off) {
  opt = (struct geneve_opt *)&opts->data[off];

  if (!(*dont_care) || opt->opt_class || opt->type ||
      memchr_inv(opt->opt_data, 0, opt->length * 4)) {
   *dont_care = false;

   if (opt->opt_class != htons(U16_MAX) ||
       opt->type != U8_MAX) {
    NL_SET_ERR_MSG_MOD(extack,
         "Partial match of tunnel options in chain > 0 isn't supported");
    netdev_warn(priv->netdev,
         "Partial match of tunnel options in chain > 0 isn't supported");
    return -EOPNOTSUPP;
   }
  }

  off += sizeof(struct geneve_opt) + opt->length * 4;
 }

 return 0;
}

#define COPY_DISSECTOR(rule, diss_key, dst)\
({ \
 struct flow_rule *__rule = (rule);\
 typeof(dst) __dst = dst;\
\
 memcpy(__dst,\
        skb_flow_dissector_target(__rule->match.dissector,\
      diss_key,\
      __rule->match.key),\
        sizeof(*__dst));\
})

static int mlx5e_get_flow_tunnel_id(struct mlx5e_priv *priv,
        struct mlx5e_tc_flow *flow,
        struct flow_cls_offload *f,
        struct net_device *filter_dev)
{
 struct flow_rule *rule = flow_cls_offload_flow_rule(f);
 struct netlink_ext_ack *extack = f->common.extack;
 struct mlx5e_tc_mod_hdr_acts *mod_hdr_acts;
 struct flow_match_enc_opts enc_opts_match;
 struct tunnel_match_enc_opts tun_enc_opts;
 struct mlx5_rep_uplink_priv *uplink_priv;
 struct mlx5_flow_attr *attr = flow->attr;
 struct mlx5e_rep_priv *uplink_rpriv;
 struct tunnel_match_key tunnel_key;
 bool enc_opts_is_dont_care = true;
 u32 tun_id, enc_opts_id = 0;
 struct mlx5_eswitch *esw;
 u32 value, mask;
 int err;

 esw = priv->mdev->priv.eswitch;
 uplink_rpriv = mlx5_eswitch_get_uplink_priv(esw, REP_ETH);
 uplink_priv = &uplink_rpriv->uplink_priv;

 memset(&tunnel_key, 0, sizeof(tunnel_key));
 COPY_DISSECTOR(rule, FLOW_DISSECTOR_KEY_ENC_CONTROL,
         &tunnel_key.enc_control);
 if (tunnel_key.enc_control.addr_type == FLOW_DISSECTOR_KEY_IPV4_ADDRS)
  COPY_DISSECTOR(rule, FLOW_DISSECTOR_KEY_ENC_IPV4_ADDRS,
          &tunnel_key.enc_ipv4);
 else
  COPY_DISSECTOR(rule, FLOW_DISSECTOR_KEY_ENC_IPV6_ADDRS,
          &tunnel_key.enc_ipv6);
 COPY_DISSECTOR(rule, FLOW_DISSECTOR_KEY_ENC_IP, &tunnel_key.enc_ip);
 COPY_DISSECTOR(rule, FLOW_DISSECTOR_KEY_ENC_PORTS,
         &tunnel_key.enc_tp);
 COPY_DISSECTOR(rule, FLOW_DISSECTOR_KEY_ENC_KEYID,
         &tunnel_key.enc_key_id);
 tunnel_key.filter_ifindex = filter_dev->ifindex;

 err = mapping_add(uplink_priv->tunnel_mapping, &tunnel_key, &tun_id);
 if (err)
  return err;

 flow_rule_match_enc_opts(rule, &enc_opts_match);
 err = enc_opts_is_dont_care_or_full_match(priv,
        enc_opts_match.mask,
        extack,
        &enc_opts_is_dont_care);
 if (err)
  goto err_enc_opts;

 if (!enc_opts_is_dont_care) {
  memset(&tun_enc_opts, 0, sizeof(tun_enc_opts));
  memcpy(&tun_enc_opts.key, enc_opts_match.key,
         sizeof(*enc_opts_match.key));
  memcpy(&tun_enc_opts.mask, enc_opts_match.mask,
         sizeof(*enc_opts_match.mask));

  err = mapping_add(uplink_priv->tunnel_enc_opts_mapping,
      &tun_enc_opts, &enc_opts_id);
  if (err)
   goto err_enc_opts;
 }

 value = tun_id << ENC_OPTS_BITS | enc_opts_id;
 mask = enc_opts_id ? TUNNEL_ID_MASK :
        (TUNNEL_ID_MASK & ~ENC_OPTS_BITS_MASK);

 if (attr->chain) {
  mlx5e_tc_match_to_reg_match(&attr->parse_attr->spec,
         TUNNEL_TO_REG, value, mask);
 } else {
  mod_hdr_acts = &attr->parse_attr->mod_hdr_acts;
  err = mlx5e_tc_match_to_reg_set(priv->mdev,
      mod_hdr_acts, MLX5_FLOW_NAMESPACE_FDB,
      TUNNEL_TO_REG, value);
  if (err)
   goto err_set;

  attr->action |= MLX5_FLOW_CONTEXT_ACTION_MOD_HDR;
 }

 flow->attr->tunnel_id = value;
 return 0;

err_set:
 if (enc_opts_id)
  mapping_remove(uplink_priv->tunnel_enc_opts_mapping,
          enc_opts_id);
err_enc_opts:
 mapping_remove(uplink_priv->tunnel_mapping, tun_id);
 return err;
}

static void mlx5e_put_flow_tunnel_id(struct mlx5e_tc_flow *flow)
{
 u32 enc_opts_id = flow->attr->tunnel_id & ENC_OPTS_BITS_MASK;
 u32 tun_id = flow->attr->tunnel_id >> ENC_OPTS_BITS;
 struct mlx5_rep_uplink_priv *uplink_priv;
 struct mlx5e_rep_priv *uplink_rpriv;
 struct mlx5_eswitch *esw;

 esw = flow->priv->mdev->priv.eswitch;
 uplink_rpriv = mlx5_eswitch_get_uplink_priv(esw, REP_ETH);
 uplink_priv = &uplink_rpriv->uplink_priv;

 if (tun_id)
  mapping_remove(uplink_priv->tunnel_mapping, tun_id);
 if (enc_opts_id)
  mapping_remove(uplink_priv->tunnel_enc_opts_mapping,
          enc_opts_id);
}

void mlx5e_tc_set_ethertype(struct mlx5_core_dev *mdev,
       struct flow_match_basic *match, bool outer,
       void *headers_c, void *headers_v)
{
 bool ip_version_cap;

 ip_version_cap = outer ?
  MLX5_CAP_FLOWTABLE_NIC_RX(mdev,
       ft_field_support.outer_ip_version) :
  MLX5_CAP_FLOWTABLE_NIC_RX(mdev,
       ft_field_support.inner_ip_version);

 if (ip_version_cap && match->mask->n_proto == htons(0xFFFF) &&
     (match->key->n_proto == htons(ETH_P_IP) ||
      match->key->n_proto == htons(ETH_P_IPV6))) {
  MLX5_SET_TO_ONES(fte_match_set_lyr_2_4, headers_c, ip_version);
  MLX5_SET(fte_match_set_lyr_2_4, headers_v, ip_version,
    match->key->n_proto == htons(ETH_P_IP) ? 4 : 6);
 } else {
  MLX5_SET(fte_match_set_lyr_2_4, headers_c, ethertype,
    ntohs(match->mask->n_proto));
  MLX5_SET(fte_match_set_lyr_2_4, headers_v, ethertype,
    ntohs(match->key->n_proto));
 }
}

u8 mlx5e_tc_get_ip_version(struct mlx5_flow_spec *spec, bool outer)
{
 void *headers_v;
 u16 ethertype;
 u8 ip_version;

 if (outer)
  headers_v = MLX5_ADDR_OF(fte_match_param, spec->match_value, outer_headers);
 else
  headers_v = MLX5_ADDR_OF(fte_match_param, spec->match_value, inner_headers);

 ip_version = MLX5_GET(fte_match_set_lyr_2_4, headers_v, ip_version);
 /* Return ip_version converted from ethertype anyway */
 if (!ip_version) {
  ethertype = MLX5_GET(fte_match_set_lyr_2_4, headers_v, ethertype);
  if (ethertype == ETH_P_IP || ethertype == ETH_P_ARP)
   ip_version = 4;
  else if (ethertype == ETH_P_IPV6)
   ip_version = 6;
 }
 return ip_version;
}

/* Tunnel device follows RFC 6040, see include/net/inet_ecn.h.
 * And changes inner ip_ecn depending on inner and outer ip_ecn as follows:
 *      +---------+----------------------------------------+
 *      |Arriving |         Arriving Outer Header          |
 *      |   Inner +---------+---------+---------+----------+
 *      |  Header | Not-ECT | ECT(0)  | ECT(1)  |   CE     |
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=99 H=89 G=94

¤ Dauer der Verarbeitung: 0.32 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge