Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Linux/drivers/net/ethernet/mellanox/mlx5/core/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 110 kB image not shown  

Quelle  fs_core.c   Sprache: C

 
/*
 * Copyright (c) 2015, 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 <linux/mutex.h>
#include <linux/mlx5/driver.h>
#include <linux/mlx5/vport.h>
#include <linux/mlx5/eswitch.h>
#include <net/devlink.h>

#include "mlx5_core.h"
#include "fs_core.h"
#include "fs_cmd.h"
#include "fs_ft_pool.h"
#include "diag/fs_tracepoint.h"
#include "devlink.h"

#define INIT_TREE_NODE_ARRAY_SIZE(...) (sizeof((struct init_tree_node[]){__VA_ARGS__}) /\
      sizeof(struct init_tree_node))

#define ADD_PRIO(num_prios_val, min_level_val, num_levels_val, caps_val,\
   ...) {.type = FS_TYPE_PRIO,\
 .min_ft_level = min_level_val,\
 .num_levels = num_levels_val,\
 .num_leaf_prios = num_prios_val,\
 .caps = caps_val,\
 .children = (struct init_tree_node[]) {__VA_ARGS__},\
 .ar_size = INIT_TREE_NODE_ARRAY_SIZE(__VA_ARGS__) \
}

#define ADD_MULTIPLE_PRIO(num_prios_val, num_levels_val, ...)\
 ADD_PRIO(num_prios_val, 0, num_levels_val, {},\
   __VA_ARGS__)\

#define ADD_NS(def_miss_act, ...) {.type = FS_TYPE_NAMESPACE, \
 .def_miss_action = def_miss_act,\
 .children = (struct init_tree_node[]) {__VA_ARGS__},\
 .ar_size = INIT_TREE_NODE_ARRAY_SIZE(__VA_ARGS__) \
}

#define INIT_CAPS_ARRAY_SIZE(...) (sizeof((long[]){__VA_ARGS__}) /\
       sizeof(long))

#define FS_CAP(cap) (__mlx5_bit_off(flow_table_nic_cap, cap))

#define FS_REQUIRED_CAPS(...) {.arr_sz = INIT_CAPS_ARRAY_SIZE(__VA_ARGS__), \
          .caps = (long[]) {__VA_ARGS__} }

#define FS_CHAINING_CAPS  FS_REQUIRED_CAPS(FS_CAP(flow_table_properties_nic_receive.flow_modify_en), \
        FS_CAP(flow_table_properties_nic_receive.modify_root), \
        FS_CAP(flow_table_properties_nic_receive.identified_miss_table_mode), \
        FS_CAP(flow_table_properties_nic_receive.flow_table_modify))

#define FS_CHAINING_CAPS_EGRESS                                                \
 FS_REQUIRED_CAPS(                                                      \
  FS_CAP(flow_table_properties_nic_transmit.flow_modify_en),     \
  FS_CAP(flow_table_properties_nic_transmit.modify_root),        \
  FS_CAP(flow_table_properties_nic_transmit                      \
          .identified_miss_table_mode),                   \
  FS_CAP(flow_table_properties_nic_transmit.flow_table_modify))

#define FS_CHAINING_CAPS_RDMA_TX                                                \
 FS_REQUIRED_CAPS(                                                       \
  FS_CAP(flow_table_properties_nic_transmit_rdma.flow_modify_en), \
  FS_CAP(flow_table_properties_nic_transmit_rdma.modify_root),    \
  FS_CAP(flow_table_properties_nic_transmit_rdma                  \
          .identified_miss_table_mode),                    \
  FS_CAP(flow_table_properties_nic_transmit_rdma                  \
          .flow_table_modify))

#define LEFTOVERS_NUM_LEVELS 1
#define LEFTOVERS_NUM_PRIOS 1

#define RDMA_RX_COUNTERS_PRIO_NUM_LEVELS 1
#define RDMA_TX_COUNTERS_PRIO_NUM_LEVELS 1

#define BY_PASS_PRIO_NUM_LEVELS 1
#define BY_PASS_MIN_LEVEL (ETHTOOL_MIN_LEVEL + MLX5_BY_PASS_NUM_PRIOS +\
      LEFTOVERS_NUM_PRIOS)

#define KERNEL_RX_MACSEC_NUM_PRIOS  1
#define KERNEL_RX_MACSEC_NUM_LEVELS 3
#define KERNEL_RX_MACSEC_MIN_LEVEL (BY_PASS_MIN_LEVEL + KERNEL_RX_MACSEC_NUM_PRIOS)

#define ETHTOOL_PRIO_NUM_LEVELS 1
#define ETHTOOL_NUM_PRIOS 11
#define ETHTOOL_MIN_LEVEL (KERNEL_MIN_LEVEL + ETHTOOL_NUM_PRIOS)
/* Vlan, mac, ttc, inner ttc, {UDP/ANY/aRFS/accel/{esp, esp_err}}, IPsec policy,
 * IPsec policy miss, {IPsec RoCE MPV,Alias table},IPsec RoCE policy
 */

#define KERNEL_NIC_PRIO_NUM_LEVELS 11
#define KERNEL_NIC_NUM_PRIOS 1
/* One more level for tc, and one more for promisc */
#define KERNEL_MIN_LEVEL (KERNEL_NIC_PRIO_NUM_LEVELS + 2)

#define KERNEL_NIC_PROMISC_NUM_PRIOS 1
#define KERNEL_NIC_PROMISC_NUM_LEVELS 1

#define KERNEL_NIC_TC_NUM_PRIOS  1
#define KERNEL_NIC_TC_NUM_LEVELS 3

#define ANCHOR_NUM_LEVELS 1
#define ANCHOR_NUM_PRIOS 1
#define ANCHOR_MIN_LEVEL (BY_PASS_MIN_LEVEL + 1)

#define OFFLOADS_MAX_FT 2
#define OFFLOADS_NUM_PRIOS 2
#define OFFLOADS_MIN_LEVEL (ANCHOR_MIN_LEVEL + OFFLOADS_NUM_PRIOS)

#define LAG_PRIO_NUM_LEVELS 1
#define LAG_NUM_PRIOS 1
#define LAG_MIN_LEVEL (OFFLOADS_MIN_LEVEL + KERNEL_RX_MACSEC_MIN_LEVEL + 1)

#define KERNEL_TX_IPSEC_NUM_PRIOS  1
#define KERNEL_TX_IPSEC_NUM_LEVELS 4
#define KERNEL_TX_IPSEC_MIN_LEVEL        (KERNEL_TX_IPSEC_NUM_LEVELS)

#define KERNEL_TX_MACSEC_NUM_PRIOS  1
#define KERNEL_TX_MACSEC_NUM_LEVELS 2
#define KERNEL_TX_MACSEC_MIN_LEVEL       (KERNEL_TX_IPSEC_MIN_LEVEL + KERNEL_TX_MACSEC_NUM_PRIOS)

struct node_caps {
 size_t arr_sz;
 long *caps;
};

static struct init_tree_node {
 enum fs_node_type type;
 struct init_tree_node *children;
 int ar_size;
 struct node_caps caps;
 int min_ft_level;
 int num_leaf_prios;
 int prio;
 int num_levels;
 enum mlx5_flow_table_miss_action def_miss_action;
} root_fs = {
 .type = FS_TYPE_NAMESPACE,
 .ar_size = 8,
   .children = (struct init_tree_node[]){
    ADD_PRIO(0, BY_PASS_MIN_LEVEL, 0, FS_CHAINING_CAPS,
      ADD_NS(MLX5_FLOW_TABLE_MISS_ACTION_DEF,
      ADD_MULTIPLE_PRIO(MLX5_BY_PASS_NUM_PRIOS,
          BY_PASS_PRIO_NUM_LEVELS))),
    ADD_PRIO(0, KERNEL_RX_MACSEC_MIN_LEVEL, 0, FS_CHAINING_CAPS,
      ADD_NS(MLX5_FLOW_TABLE_MISS_ACTION_DEF,
      ADD_MULTIPLE_PRIO(KERNEL_RX_MACSEC_NUM_PRIOS,
          KERNEL_RX_MACSEC_NUM_LEVELS))),
    ADD_PRIO(0, LAG_MIN_LEVEL, 0, FS_CHAINING_CAPS,
      ADD_NS(MLX5_FLOW_TABLE_MISS_ACTION_DEF,
      ADD_MULTIPLE_PRIO(LAG_NUM_PRIOS,
          LAG_PRIO_NUM_LEVELS))),
    ADD_PRIO(0, OFFLOADS_MIN_LEVEL, 0, FS_CHAINING_CAPS,
      ADD_NS(MLX5_FLOW_TABLE_MISS_ACTION_DEF,
      ADD_MULTIPLE_PRIO(OFFLOADS_NUM_PRIOS,
          OFFLOADS_MAX_FT))),
    ADD_PRIO(0, ETHTOOL_MIN_LEVEL, 0, FS_CHAINING_CAPS,
      ADD_NS(MLX5_FLOW_TABLE_MISS_ACTION_DEF,
      ADD_MULTIPLE_PRIO(ETHTOOL_NUM_PRIOS,
          ETHTOOL_PRIO_NUM_LEVELS))),
    ADD_PRIO(0, KERNEL_MIN_LEVEL, 0, {},
      ADD_NS(MLX5_FLOW_TABLE_MISS_ACTION_DEF,
      ADD_MULTIPLE_PRIO(KERNEL_NIC_TC_NUM_PRIOS,
          KERNEL_NIC_TC_NUM_LEVELS),
      ADD_MULTIPLE_PRIO(KERNEL_NIC_PROMISC_NUM_PRIOS,
          KERNEL_NIC_PROMISC_NUM_LEVELS),
      ADD_MULTIPLE_PRIO(KERNEL_NIC_NUM_PRIOS,
          KERNEL_NIC_PRIO_NUM_LEVELS))),
    ADD_PRIO(0, BY_PASS_MIN_LEVEL, 0, FS_CHAINING_CAPS,
      ADD_NS(MLX5_FLOW_TABLE_MISS_ACTION_DEF,
      ADD_MULTIPLE_PRIO(LEFTOVERS_NUM_PRIOS,
          LEFTOVERS_NUM_LEVELS))),
    ADD_PRIO(0, ANCHOR_MIN_LEVEL, 0, {},
      ADD_NS(MLX5_FLOW_TABLE_MISS_ACTION_DEF,
      ADD_MULTIPLE_PRIO(ANCHOR_NUM_PRIOS,
          ANCHOR_NUM_LEVELS))),
 }
};

static struct init_tree_node egress_root_fs = {
 .type = FS_TYPE_NAMESPACE,
 .ar_size = 3,
 .children = (struct init_tree_node[]) {
  ADD_PRIO(0, MLX5_BY_PASS_NUM_PRIOS, 0,
    FS_CHAINING_CAPS_EGRESS,
    ADD_NS(MLX5_FLOW_TABLE_MISS_ACTION_DEF,
    ADD_MULTIPLE_PRIO(MLX5_BY_PASS_NUM_PRIOS,
        BY_PASS_PRIO_NUM_LEVELS))),
  ADD_PRIO(0, KERNEL_TX_IPSEC_MIN_LEVEL, 0,
    FS_CHAINING_CAPS_EGRESS,
    ADD_NS(MLX5_FLOW_TABLE_MISS_ACTION_DEF,
    ADD_MULTIPLE_PRIO(KERNEL_TX_IPSEC_NUM_PRIOS,
        KERNEL_TX_IPSEC_NUM_LEVELS))),
  ADD_PRIO(0, KERNEL_TX_MACSEC_MIN_LEVEL, 0,
    FS_CHAINING_CAPS_EGRESS,
    ADD_NS(MLX5_FLOW_TABLE_MISS_ACTION_DEF,
    ADD_MULTIPLE_PRIO(KERNEL_TX_MACSEC_NUM_PRIOS,
        KERNEL_TX_MACSEC_NUM_LEVELS))),
 }
};

enum {
 RDMA_RX_IPSEC_PRIO,
 RDMA_RX_MACSEC_PRIO,
 RDMA_RX_COUNTERS_PRIO,
 RDMA_RX_BYPASS_PRIO,
 RDMA_RX_KERNEL_PRIO,
};

#define RDMA_RX_IPSEC_NUM_PRIOS 1
#define RDMA_RX_IPSEC_NUM_LEVELS 4
#define RDMA_RX_IPSEC_MIN_LEVEL  (RDMA_RX_IPSEC_NUM_LEVELS)

#define RDMA_RX_BYPASS_MIN_LEVEL MLX5_BY_PASS_NUM_REGULAR_PRIOS
#define RDMA_RX_KERNEL_MIN_LEVEL (RDMA_RX_BYPASS_MIN_LEVEL + 1)
#define RDMA_RX_COUNTERS_MIN_LEVEL (RDMA_RX_KERNEL_MIN_LEVEL + 2)

#define RDMA_RX_MACSEC_NUM_PRIOS 1
#define RDMA_RX_MACSEC_PRIO_NUM_LEVELS 2
#define RDMA_RX_MACSEC_MIN_LEVEL  (RDMA_RX_COUNTERS_MIN_LEVEL + RDMA_RX_MACSEC_NUM_PRIOS)

static struct init_tree_node rdma_rx_root_fs = {
 .type = FS_TYPE_NAMESPACE,
 .ar_size = 5,
 .children = (struct init_tree_node[]) {
  [RDMA_RX_IPSEC_PRIO] =
  ADD_PRIO(0, RDMA_RX_IPSEC_MIN_LEVEL, 0,
    FS_CHAINING_CAPS,
    ADD_NS(MLX5_FLOW_TABLE_MISS_ACTION_DEF,
    ADD_MULTIPLE_PRIO(RDMA_RX_IPSEC_NUM_PRIOS,
        RDMA_RX_IPSEC_NUM_LEVELS))),
  [RDMA_RX_MACSEC_PRIO] =
  ADD_PRIO(0, RDMA_RX_MACSEC_MIN_LEVEL, 0,
    FS_CHAINING_CAPS,
    ADD_NS(MLX5_FLOW_TABLE_MISS_ACTION_DEF,
    ADD_MULTIPLE_PRIO(RDMA_RX_MACSEC_NUM_PRIOS,
        RDMA_RX_MACSEC_PRIO_NUM_LEVELS))),
  [RDMA_RX_COUNTERS_PRIO] =
  ADD_PRIO(0, RDMA_RX_COUNTERS_MIN_LEVEL, 0,
    FS_CHAINING_CAPS,
    ADD_NS(MLX5_FLOW_TABLE_MISS_ACTION_DEF,
    ADD_MULTIPLE_PRIO(MLX5_RDMA_RX_NUM_COUNTERS_PRIOS,
        RDMA_RX_COUNTERS_PRIO_NUM_LEVELS))),
  [RDMA_RX_BYPASS_PRIO] =
  ADD_PRIO(0, RDMA_RX_BYPASS_MIN_LEVEL, 0,
    FS_CHAINING_CAPS,
    ADD_NS(MLX5_FLOW_TABLE_MISS_ACTION_DEF,
    ADD_MULTIPLE_PRIO(MLX5_BY_PASS_NUM_REGULAR_PRIOS,
        BY_PASS_PRIO_NUM_LEVELS))),
  [RDMA_RX_KERNEL_PRIO] =
  ADD_PRIO(0, RDMA_RX_KERNEL_MIN_LEVEL, 0,
    FS_CHAINING_CAPS,
    ADD_NS(MLX5_FLOW_TABLE_MISS_ACTION_SWITCH_DOMAIN,
    ADD_MULTIPLE_PRIO(1, 1))),
 }
};

enum {
 RDMA_TX_COUNTERS_PRIO,
 RDMA_TX_IPSEC_PRIO,
 RDMA_TX_MACSEC_PRIO,
 RDMA_TX_BYPASS_PRIO,
};

#define RDMA_TX_BYPASS_MIN_LEVEL MLX5_BY_PASS_NUM_PRIOS
#define RDMA_TX_COUNTERS_MIN_LEVEL (RDMA_TX_BYPASS_MIN_LEVEL + 1)

#define RDMA_TX_IPSEC_NUM_PRIOS 2
#define RDMA_TX_IPSEC_PRIO_NUM_LEVELS 1
#define RDMA_TX_IPSEC_MIN_LEVEL  (RDMA_TX_COUNTERS_MIN_LEVEL + RDMA_TX_IPSEC_NUM_PRIOS)

#define RDMA_TX_MACSEC_NUM_PRIOS 1
#define RDMA_TX_MACESC_PRIO_NUM_LEVELS 1
#define RDMA_TX_MACSEC_MIN_LEVEL  (RDMA_TX_COUNTERS_MIN_LEVEL + RDMA_TX_MACSEC_NUM_PRIOS)

static struct init_tree_node rdma_tx_root_fs = {
 .type = FS_TYPE_NAMESPACE,
 .ar_size = 4,
 .children = (struct init_tree_node[]) {
  [RDMA_TX_COUNTERS_PRIO] =
  ADD_PRIO(0, RDMA_TX_COUNTERS_MIN_LEVEL, 0,
    FS_CHAINING_CAPS,
    ADD_NS(MLX5_FLOW_TABLE_MISS_ACTION_DEF,
    ADD_MULTIPLE_PRIO(MLX5_RDMA_TX_NUM_COUNTERS_PRIOS,
        RDMA_TX_COUNTERS_PRIO_NUM_LEVELS))),
  [RDMA_TX_IPSEC_PRIO] =
  ADD_PRIO(0, RDMA_TX_IPSEC_MIN_LEVEL, 0,
    FS_CHAINING_CAPS,
    ADD_NS(MLX5_FLOW_TABLE_MISS_ACTION_DEF,
    ADD_MULTIPLE_PRIO(RDMA_TX_IPSEC_NUM_PRIOS,
        RDMA_TX_IPSEC_PRIO_NUM_LEVELS))),
  [RDMA_TX_MACSEC_PRIO] =
  ADD_PRIO(0, RDMA_TX_MACSEC_MIN_LEVEL, 0,
    FS_CHAINING_CAPS,
    ADD_NS(MLX5_FLOW_TABLE_MISS_ACTION_DEF,
    ADD_MULTIPLE_PRIO(RDMA_TX_MACSEC_NUM_PRIOS,
        RDMA_TX_MACESC_PRIO_NUM_LEVELS))),
  [RDMA_TX_BYPASS_PRIO] =
  ADD_PRIO(0, RDMA_TX_BYPASS_MIN_LEVEL, 0,
    FS_CHAINING_CAPS_RDMA_TX,
    ADD_NS(MLX5_FLOW_TABLE_MISS_ACTION_DEF,
    ADD_MULTIPLE_PRIO(RDMA_TX_BYPASS_MIN_LEVEL,
        BY_PASS_PRIO_NUM_LEVELS))),
 }
};

enum fs_i_lock_class {
 FS_LOCK_GRANDPARENT,
 FS_LOCK_PARENT,
 FS_LOCK_CHILD
};

static const struct rhashtable_params rhash_fte = {
 .key_len = sizeof_field(struct fs_fte, val),
 .key_offset = offsetof(struct fs_fte, val),
 .head_offset = offsetof(struct fs_fte, hash),
 .automatic_shrinking = true,
 .min_size = 1,
};

static const struct rhashtable_params rhash_fg = {
 .key_len = sizeof_field(struct mlx5_flow_group, mask),
 .key_offset = offsetof(struct mlx5_flow_group, mask),
 .head_offset = offsetof(struct mlx5_flow_group, hash),
 .automatic_shrinking = true,
 .min_size = 1,

};

static void del_hw_flow_table(struct fs_node *node);
static void del_hw_flow_group(struct fs_node *node);
static void del_hw_fte(struct fs_node *node);
static void del_sw_flow_table(struct fs_node *node);
static void del_sw_flow_group(struct fs_node *node);
static void del_sw_fte(struct fs_node *node);
static void del_sw_prio(struct fs_node *node);
static void del_sw_ns(struct fs_node *node);
/* Delete rule (destination) is special case that
 * requires to lock the FTE for all the deletion process.
 */

static void del_sw_hw_rule(struct fs_node *node);
static bool mlx5_flow_dests_cmp(struct mlx5_flow_destination *d1,
    struct mlx5_flow_destination *d2);
static void cleanup_root_ns(struct mlx5_flow_root_namespace *root_ns);
static struct mlx5_flow_rule *
find_flow_rule(struct fs_fte *fte,
        struct mlx5_flow_destination *dest);

static void tree_init_node(struct fs_node *node,
      void (*del_hw_func)(struct fs_node *),
      void (*del_sw_func)(struct fs_node *))
{
 refcount_set(&node->refcount, 1);
 INIT_LIST_HEAD(&node->list);
 INIT_LIST_HEAD(&node->children);
 init_rwsem(&node->lock);
 node->del_hw_func = del_hw_func;
 node->del_sw_func = del_sw_func;
 node->active = false;
}

static void tree_add_node(struct fs_node *node, struct fs_node *parent)
{
 if (parent)
  refcount_inc(&parent->refcount);
 node->parent = parent;

 /* Parent is the root */
 if (!parent)
  node->root = node;
 else
  node->root = parent->root;
}

static int tree_get_node(struct fs_node *node)
{
 return refcount_inc_not_zero(&node->refcount);
}

static void nested_down_read_ref_node(struct fs_node *node,
          enum fs_i_lock_class class)
{
 if (node) {
  down_read_nested(&node->lock, class);
  refcount_inc(&node->refcount);
 }
}

static void nested_down_write_ref_node(struct fs_node *node,
           enum fs_i_lock_class class)
{
 if (node) {
  down_write_nested(&node->lock, class);
  refcount_inc(&node->refcount);
 }
}

static void down_write_ref_node(struct fs_node *node, bool locked)
{
 if (node) {
  if (!locked)
   down_write(&node->lock);
  refcount_inc(&node->refcount);
 }
}

static void up_read_ref_node(struct fs_node *node)
{
 refcount_dec(&node->refcount);
 up_read(&node->lock);
}

static void up_write_ref_node(struct fs_node *node, bool locked)
{
 refcount_dec(&node->refcount);
 if (!locked)
  up_write(&node->lock);
}

static void tree_put_node(struct fs_node *node, bool locked)
{
 struct fs_node *parent_node = node->parent;

 if (refcount_dec_and_test(&node->refcount)) {
  if (node->del_hw_func)
   node->del_hw_func(node);
  if (parent_node) {
   down_write_ref_node(parent_node, locked);
   list_del_init(&node->list);
  }
  node->del_sw_func(node);
  if (parent_node)
   up_write_ref_node(parent_node, locked);
  node = NULL;
 }
 if (!node && parent_node)
  tree_put_node(parent_node, locked);
}

static int tree_remove_node(struct fs_node *node, bool locked)
{
 if (refcount_read(&node->refcount) > 1) {
  refcount_dec(&node->refcount);
  return -EEXIST;
 }
 tree_put_node(node, locked);
 return 0;
}

static struct fs_prio *find_prio(struct mlx5_flow_namespace *ns,
     unsigned int prio)
{
 struct fs_prio *iter_prio;

 fs_for_each_prio(iter_prio, ns) {
  if (iter_prio->prio == prio)
   return iter_prio;
 }

 return NULL;
}

static bool is_fwd_next_action(u32 action)
{
 return action & (MLX5_FLOW_CONTEXT_ACTION_FWD_NEXT_PRIO |
    MLX5_FLOW_CONTEXT_ACTION_FWD_NEXT_NS);
}

static bool is_fwd_dest_type(enum mlx5_flow_destination_type type)
{
 return type == MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE_NUM ||
  type == MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE ||
  type == MLX5_FLOW_DESTINATION_TYPE_UPLINK ||
  type == MLX5_FLOW_DESTINATION_TYPE_VPORT ||
  type == MLX5_FLOW_DESTINATION_TYPE_FLOW_SAMPLER ||
  type == MLX5_FLOW_DESTINATION_TYPE_TIR ||
  type == MLX5_FLOW_DESTINATION_TYPE_RANGE ||
  type == MLX5_FLOW_DESTINATION_TYPE_TABLE_TYPE;
}

static bool check_valid_spec(const struct mlx5_flow_spec *spec)
{
 int i;

 for (i = 0; i < MLX5_ST_SZ_DW_MATCH_PARAM; i++)
  if (spec->match_value[i] & ~spec->match_criteria[i]) {
   pr_warn("mlx5_core: match_value differs from match_criteria\n");
   return false;
  }

 return true;
}

struct mlx5_flow_root_namespace *find_root(struct fs_node *node)
{
 struct fs_node *root;
 struct mlx5_flow_namespace *ns;

 root = node->root;

 if (WARN_ON(root->type != FS_TYPE_NAMESPACE)) {
  pr_warn("mlx5: flow steering node is not in tree or garbaged\n");
  return NULL;
 }

 ns = container_of(root, struct mlx5_flow_namespace, node);
 return container_of(ns, struct mlx5_flow_root_namespace, ns);
}

static inline struct mlx5_flow_steering *get_steering(struct fs_node *node)
{
 struct mlx5_flow_root_namespace *root = find_root(node);

 if (root)
  return root->dev->priv.steering;
 return NULL;
}

static inline struct mlx5_core_dev *get_dev(struct fs_node *node)
{
 struct mlx5_flow_root_namespace *root = find_root(node);

 if (root)
  return root->dev;
 return NULL;
}

static void del_sw_ns(struct fs_node *node)
{
 kfree(node);
}

static void del_sw_prio(struct fs_node *node)
{
 kfree(node);
}

static void del_hw_flow_table(struct fs_node *node)
{
 struct mlx5_flow_root_namespace *root;
 struct mlx5_flow_table *ft;
 struct mlx5_core_dev *dev;
 int err;

 fs_get_obj(ft, node);
 dev = get_dev(&ft->node);
 root = find_root(&ft->node);
 trace_mlx5_fs_del_ft(ft);

 if (node->active) {
  err = root->cmds->destroy_flow_table(root, ft);
  if (err)
   mlx5_core_warn(dev, "flow steering can't destroy ft\n");
 }
}

static void del_sw_flow_table(struct fs_node *node)
{
 struct mlx5_flow_table *ft;
 struct fs_prio *prio;

 fs_get_obj(ft, node);

 rhltable_destroy(&ft->fgs_hash);
 if (ft->node.parent) {
  fs_get_obj(prio, ft->node.parent);
  prio->num_ft--;
 }
 kfree(ft);
}

static void modify_fte(struct fs_fte *fte)
{
 struct mlx5_flow_root_namespace *root;
 struct mlx5_flow_table *ft;
 struct mlx5_flow_group *fg;
 struct mlx5_core_dev *dev;
 int err;

 fs_get_obj(fg, fte->node.parent);
 fs_get_obj(ft, fg->node.parent);
 dev = get_dev(&fte->node);

 root = find_root(&ft->node);
 err = root->cmds->update_fte(root, ft, fg, fte->act_dests.modify_mask, fte);
 if (err)
  mlx5_core_warn(dev,
          "%s can't del rule fg id=%d fte_index=%d\n",
          __func__, fg->id, fte->index);
 fte->act_dests.modify_mask = 0;
}

static void del_sw_hw_dup_rule(struct fs_node *node)
{
 struct mlx5_flow_rule *rule;
 struct fs_fte *fte;

 fs_get_obj(rule, node);
 fs_get_obj(fte, rule->node.parent);
 trace_mlx5_fs_del_rule(rule);

 if (is_fwd_next_action(rule->sw_action)) {
  mutex_lock(&rule->dest_attr.ft->lock);
  list_del(&rule->next_ft);
  mutex_unlock(&rule->dest_attr.ft->lock);
 }

 /* If a pending rule is being deleted it means
 * this is a NO APPEND rule, so there are no partial deletions,
 * all the rules of the mlx5_flow_handle are going to be deleted
 * and the rules aren't shared with any other mlx5_flow_handle instance
 * so no need to do any bookkeeping like in del_sw_hw_rule().
 */


 kfree(rule);
}

static void del_sw_hw_rule(struct fs_node *node)
{
 struct mlx5_flow_rule *rule;
 struct fs_fte *fte;

 fs_get_obj(rule, node);
 fs_get_obj(fte, rule->node.parent);
 trace_mlx5_fs_del_rule(rule);
 if (is_fwd_next_action(rule->sw_action)) {
  mutex_lock(&rule->dest_attr.ft->lock);
  list_del(&rule->next_ft);
  mutex_unlock(&rule->dest_attr.ft->lock);
 }

 if (rule->dest_attr.type == MLX5_FLOW_DESTINATION_TYPE_COUNTER) {
  --fte->act_dests.dests_size;
  fte->act_dests.modify_mask |=
   BIT(MLX5_SET_FTE_MODIFY_ENABLE_MASK_ACTION) |
   BIT(MLX5_SET_FTE_MODIFY_ENABLE_MASK_FLOW_COUNTERS);
  fte->act_dests.action.action &= ~MLX5_FLOW_CONTEXT_ACTION_COUNT;
  mlx5_fc_local_put(rule->dest_attr.counter);
  goto out;
 }

 if (rule->dest_attr.type == MLX5_FLOW_DESTINATION_TYPE_PORT) {
  --fte->act_dests.dests_size;
  fte->act_dests.modify_mask |= BIT(MLX5_SET_FTE_MODIFY_ENABLE_MASK_ACTION);
  fte->act_dests.action.action &= ~MLX5_FLOW_CONTEXT_ACTION_ALLOW;
  goto out;
 }

 if (is_fwd_dest_type(rule->dest_attr.type)) {
  --fte->act_dests.dests_size;
  --fte->act_dests.fwd_dests;

  if (!fte->act_dests.fwd_dests)
   fte->act_dests.action.action &=
    ~MLX5_FLOW_CONTEXT_ACTION_FWD_DEST;
  fte->act_dests.modify_mask |=
   BIT(MLX5_SET_FTE_MODIFY_ENABLE_MASK_DESTINATION_LIST);
  goto out;
 }
out:
 kfree(rule);
}

static void switch_to_pending_act_dests(struct fs_fte *fte)
{
 struct fs_node *iter;

 memcpy(&fte->act_dests, &fte->dup->act_dests, sizeof(fte->act_dests));

 list_bulk_move_tail(&fte->node.children,
       fte->dup->children.next,
       fte->dup->children.prev);

 list_for_each_entry(iter, &fte->node.children, list)
  iter->del_sw_func = del_sw_hw_rule;

 /* Make sure the fte isn't deleted
 * as mlx5_del_flow_rules() decreases the refcount
 * of the fte to trigger deletion.
 */

 tree_get_node(&fte->node);
}

static void del_hw_fte(struct fs_node *node)
{
 struct mlx5_flow_root_namespace *root;
 struct mlx5_flow_table *ft;
 struct mlx5_flow_group *fg;
 struct mlx5_core_dev *dev;
 bool pending_used = false;
 struct fs_fte *fte;
 int err;

 fs_get_obj(fte, node);
 fs_get_obj(fg, fte->node.parent);
 fs_get_obj(ft, fg->node.parent);

 trace_mlx5_fs_del_fte(fte);
 WARN_ON(fte->act_dests.dests_size);
 dev = get_dev(&ft->node);
 root = find_root(&ft->node);

 if (fte->dup && !list_empty(&fte->dup->children)) {
  switch_to_pending_act_dests(fte);
  pending_used = true;
 } else {
  /* Avoid double call to del_hw_fte */
  node->del_hw_func = NULL;
 }

 if (node->active) {
  if (pending_used) {
   err = root->cmds->update_fte(root, ft, fg,
           fte->act_dests.modify_mask, fte);
   if (err)
    mlx5_core_warn(dev,
            "flow steering can't update to pending rule in index %d of flow group id %d\n",
            fte->index, fg->id);
   fte->act_dests.modify_mask = 0;
  } else {
   err = root->cmds->delete_fte(root, ft, fte);
   if (err)
    mlx5_core_warn(dev,
            "flow steering can't delete fte in index %d of flow group id %d\n",
            fte->index, fg->id);
   node->active = false;
  }
 }
}

static void del_sw_fte(struct fs_node *node)
{
 struct mlx5_flow_steering *steering = get_steering(node);
 struct mlx5_flow_group *fg;
 struct fs_fte *fte;
 int err;

 fs_get_obj(fte, node);
 fs_get_obj(fg, fte->node.parent);

 err = rhashtable_remove_fast(&fg->ftes_hash,
         &fte->hash,
         rhash_fte);
 WARN_ON(err);
 ida_free(&fg->fte_allocator, fte->index - fg->start_index);
 kvfree(fte->dup);
 kmem_cache_free(steering->ftes_cache, fte);
}

static void del_hw_flow_group(struct fs_node *node)
{
 struct mlx5_flow_root_namespace *root;
 struct mlx5_flow_group *fg;
 struct mlx5_flow_table *ft;
 struct mlx5_core_dev *dev;

 fs_get_obj(fg, node);
 fs_get_obj(ft, fg->node.parent);
 dev = get_dev(&ft->node);
 trace_mlx5_fs_del_fg(fg);

 root = find_root(&ft->node);
 if (fg->node.active && root->cmds->destroy_flow_group(root, ft, fg))
  mlx5_core_warn(dev, "flow steering can't destroy fg %d of ft %d\n",
          fg->id, ft->id);
}

static void del_sw_flow_group(struct fs_node *node)
{
 struct mlx5_flow_steering *steering = get_steering(node);
 struct mlx5_flow_group *fg;
 struct mlx5_flow_table *ft;
 int err;

 fs_get_obj(fg, node);
 fs_get_obj(ft, fg->node.parent);

 rhashtable_destroy(&fg->ftes_hash);
 ida_destroy(&fg->fte_allocator);
 if (ft->autogroup.active &&
     fg->max_ftes == ft->autogroup.group_size &&
     fg->start_index < ft->autogroup.max_fte)
  ft->autogroup.num_groups--;
 err = rhltable_remove(&ft->fgs_hash,
         &fg->hash,
         rhash_fg);
 WARN_ON(err);
 kmem_cache_free(steering->fgs_cache, fg);
}

static int insert_fte(struct mlx5_flow_group *fg, struct fs_fte *fte)
{
 int index;
 int ret;

 index = ida_alloc_max(&fg->fte_allocator, fg->max_ftes - 1, GFP_KERNEL);
 if (index < 0)
  return index;

 fte->index = index + fg->start_index;
retry_insert:
 ret = rhashtable_insert_fast(&fg->ftes_hash,
         &fte->hash,
         rhash_fte);
 if (ret) {
  if (ret == -EBUSY) {
   cond_resched();
   goto retry_insert;
  }
  goto err_ida_remove;
 }

 tree_add_node(&fte->node, &fg->node);
 list_add_tail(&fte->node.list, &fg->node.children);
 return 0;

err_ida_remove:
 ida_free(&fg->fte_allocator, index);
 return ret;
}

static struct fs_fte *alloc_fte(struct mlx5_flow_table *ft,
    const struct mlx5_flow_spec *spec,
    struct mlx5_flow_act *flow_act)
{
 struct mlx5_flow_steering *steering = get_steering(&ft->node);
 struct fs_fte *fte;

 fte = kmem_cache_zalloc(steering->ftes_cache, GFP_KERNEL);
 if (!fte)
  return ERR_PTR(-ENOMEM);

 memcpy(fte->val, &spec->match_value, sizeof(fte->val));
 fte->node.type =  FS_TYPE_FLOW_ENTRY;
 fte->act_dests.action = *flow_act;
 fte->act_dests.flow_context = spec->flow_context;

 tree_init_node(&fte->node, del_hw_fte, del_sw_fte);

 return fte;
}

static void dealloc_flow_group(struct mlx5_flow_steering *steering,
          struct mlx5_flow_group *fg)
{
 rhashtable_destroy(&fg->ftes_hash);
 kmem_cache_free(steering->fgs_cache, fg);
}

static struct mlx5_flow_group *alloc_flow_group(struct mlx5_flow_steering *steering,
      u8 match_criteria_enable,
      const void *match_criteria,
      int start_index,
      int end_index)
{
 struct mlx5_flow_group *fg;
 int ret;

 fg = kmem_cache_zalloc(steering->fgs_cache, GFP_KERNEL);
 if (!fg)
  return ERR_PTR(-ENOMEM);

 ret = rhashtable_init(&fg->ftes_hash, &rhash_fte);
 if (ret) {
  kmem_cache_free(steering->fgs_cache, fg);
  return ERR_PTR(ret);
 }

 ida_init(&fg->fte_allocator);
 fg->mask.match_criteria_enable = match_criteria_enable;
 memcpy(&fg->mask.match_criteria, match_criteria,
        sizeof(fg->mask.match_criteria));
 fg->node.type =  FS_TYPE_FLOW_GROUP;
 fg->start_index = start_index;
 fg->max_ftes = end_index - start_index + 1;

 return fg;
}

static struct mlx5_flow_group *alloc_insert_flow_group(struct mlx5_flow_table *ft,
             u8 match_criteria_enable,
             const void *match_criteria,
             int start_index,
             int end_index,
             struct list_head *prev)
{
 struct mlx5_flow_steering *steering = get_steering(&ft->node);
 struct mlx5_flow_group *fg;
 int ret;

 fg = alloc_flow_group(steering, match_criteria_enable, match_criteria,
         start_index, end_index);
 if (IS_ERR(fg))
  return fg;

 /* initialize refcnt, add to parent list */
 ret = rhltable_insert(&ft->fgs_hash,
         &fg->hash,
         rhash_fg);
 if (ret) {
  dealloc_flow_group(steering, fg);
  return ERR_PTR(ret);
 }

 tree_init_node(&fg->node, del_hw_flow_group, del_sw_flow_group);
 tree_add_node(&fg->node, &ft->node);
 /* Add node to group list */
 list_add(&fg->node.list, prev);
 atomic_inc(&ft->node.version);

 return fg;
}

static struct mlx5_flow_table *alloc_flow_table(int level, u16 vport,
      enum fs_flow_table_type table_type,
      enum fs_flow_table_op_mod op_mod,
      u32 flags)
{
 struct mlx5_flow_table *ft;
 int ret;

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

 ret = rhltable_init(&ft->fgs_hash, &rhash_fg);
 if (ret) {
  kfree(ft);
  return ERR_PTR(ret);
 }

 ft->level = level;
 ft->node.type = FS_TYPE_FLOW_TABLE;
 ft->op_mod = op_mod;
 ft->type = table_type;
 ft->vport = vport;
 ft->flags = flags;
 INIT_LIST_HEAD(&ft->fwd_rules);
 mutex_init(&ft->lock);

 return ft;
}

/* If reverse is false, then we search for the first flow table in the
 * root sub-tree from start(closest from right), else we search for the
 * last flow table in the root sub-tree till start(closest from left).
 */

static struct mlx5_flow_table *find_closest_ft_recursive(struct fs_node  *root,
        struct list_head *start,
        bool reverse)
{
#define list_advance_entry(pos, reverse)  \
 ((reverse) ? list_prev_entry(pos, list) : list_next_entry(pos, list))

#define list_for_each_advance_continue(pos, head, reverse) \
 for (pos = list_advance_entry(pos, reverse);  \
      &pos->list != (head);    \
      pos = list_advance_entry(pos, reverse))

 struct fs_node *iter = list_entry(start, struct fs_node, list);
 struct mlx5_flow_table *ft = NULL;

 if (!root)
  return NULL;

 list_for_each_advance_continue(iter, &root->children, reverse) {
  if (iter->type == FS_TYPE_FLOW_TABLE) {
   fs_get_obj(ft, iter);
   return ft;
  }
  ft = find_closest_ft_recursive(iter, &iter->children, reverse);
  if (ft)
   return ft;
 }

 return ft;
}

static struct fs_node *find_prio_chains_parent(struct fs_node *parent,
            struct fs_node **child)
{
 struct fs_node *node = NULL;

 while (parent && parent->type != FS_TYPE_PRIO_CHAINS) {
  node = parent;
  parent = parent->parent;
 }

 if (child)
  *child = node;

 return parent;
}

/* If reverse is false then return the first flow table next to the passed node
 * in the tree, else return the last flow table before the node in the tree.
 * If skip is true, skip the flow tables in the same prio_chains prio.
 */

static struct mlx5_flow_table *find_closest_ft(struct fs_node *node, bool reverse,
            bool skip)
{
 struct fs_node *prio_chains_parent = NULL;
 struct mlx5_flow_table *ft = NULL;
 struct fs_node *curr_node;
 struct fs_node *parent;

 if (skip)
  prio_chains_parent = find_prio_chains_parent(node, NULL);
 parent = node->parent;
 curr_node = node;
 while (!ft && parent) {
  if (parent != prio_chains_parent)
   ft = find_closest_ft_recursive(parent, &curr_node->list,
             reverse);
  curr_node = parent;
  parent = curr_node->parent;
 }
 return ft;
}

/* Assuming all the tree is locked by mutex chain lock */
static struct mlx5_flow_table *find_next_chained_ft(struct fs_node *node)
{
 return find_closest_ft(node, falsetrue);
}

/* Assuming all the tree is locked by mutex chain lock */
static struct mlx5_flow_table *find_prev_chained_ft(struct fs_node *node)
{
 return find_closest_ft(node, truetrue);
}

static struct mlx5_flow_table *find_next_fwd_ft(struct mlx5_flow_table *ft,
      struct mlx5_flow_act *flow_act)
{
 struct fs_prio *prio;
 bool next_ns;

 next_ns = flow_act->action & MLX5_FLOW_CONTEXT_ACTION_FWD_NEXT_NS;
 fs_get_obj(prio, next_ns ? ft->ns->node.parent : ft->node.parent);

 return find_next_chained_ft(&prio->node);
}

static int connect_fts_in_prio(struct mlx5_core_dev *dev,
          struct fs_prio *prio,
          struct mlx5_flow_table *ft)
{
 struct mlx5_flow_root_namespace *root = find_root(&prio->node);
 struct mlx5_flow_table *iter;
 int err;

 fs_for_each_ft(iter, prio) {
  err = root->cmds->modify_flow_table(root, iter, ft);
  if (err) {
   mlx5_core_err(dev,
          "Failed to modify flow table id %d, type %d, err %d\n",
          iter->id, iter->type, err);
   /* The driver is out of sync with the FW */
   return err;
  }
 }
 return 0;
}

static struct mlx5_flow_table *find_closet_ft_prio_chains(struct fs_node *node,
         struct fs_node *parent,
         struct fs_node **child,
         bool reverse)
{
 struct mlx5_flow_table *ft;

 ft = find_closest_ft(node, reverse, false);

 if (ft && parent == find_prio_chains_parent(&ft->node, child))
  return ft;

 return NULL;
}

/* Connect flow tables from previous priority of prio to ft */
static int connect_prev_fts(struct mlx5_core_dev *dev,
       struct mlx5_flow_table *ft,
       struct fs_prio *prio)
{
 struct fs_node *prio_parent, *parent = NULL, *child, *node;
 struct mlx5_flow_table *prev_ft;
 int err = 0;

 prio_parent = find_prio_chains_parent(&prio->node, &child);

 /* return directly if not under the first sub ns of prio_chains prio */
 if (prio_parent && !list_is_first(&child->list, &prio_parent->children))
  return 0;

 prev_ft = find_prev_chained_ft(&prio->node);
 while (prev_ft) {
  struct fs_prio *prev_prio;

  fs_get_obj(prev_prio, prev_ft->node.parent);
  err = connect_fts_in_prio(dev, prev_prio, ft);
  if (err)
   break;

  if (!parent) {
   parent = find_prio_chains_parent(&prev_prio->node, &child);
   if (!parent)
    break;
  }

  node = child;
  prev_ft = find_closet_ft_prio_chains(node, parent, &child, true);
 }
 return err;
}

static int update_root_ft_create(struct mlx5_flow_table *ft, struct fs_prio
     *prio)
{
 struct mlx5_flow_root_namespace *root = find_root(&prio->node);
 struct mlx5_ft_underlay_qp *uqp;
 int min_level = INT_MAX;
 int err = 0;
 u32 qpn;

 if (root->root_ft)
  min_level = root->root_ft->level;

 if (ft->level >= min_level)
  return 0;

 if (list_empty(&root->underlay_qpns)) {
  /* Don't set any QPN (zero) in case QPN list is empty */
  qpn = 0;
  err = root->cmds->update_root_ft(root, ft, qpn, false);
 } else {
  list_for_each_entry(uqp, &root->underlay_qpns, list) {
   qpn = uqp->qpn;
   err = root->cmds->update_root_ft(root, ft,
        qpn, false);
   if (err)
    break;
  }
 }

 if (err)
  mlx5_core_warn(root->dev,
          "Update root flow table of id(%u) qpn(%d) failed\n",
          ft->id, qpn);
 else
  root->root_ft = ft;

 return err;
}

static bool rule_is_pending(struct fs_fte *fte, struct mlx5_flow_rule *rule)
{
 struct mlx5_flow_rule *tmp_rule;
 struct fs_node *iter;

 if (!fte->dup || list_empty(&fte->dup->children))
  return false;

 list_for_each_entry(iter, &fte->dup->children, list) {
  tmp_rule = container_of(iter, struct mlx5_flow_rule, node);

  if (tmp_rule == rule)
   return true;
 }

 return false;
}

static int _mlx5_modify_rule_destination(struct mlx5_flow_rule *rule,
      struct mlx5_flow_destination *dest)
{
 struct mlx5_flow_root_namespace *root;
 struct fs_fte_action *act_dests;
 struct mlx5_flow_table *ft;
 struct mlx5_flow_group *fg;
 bool pending = false;
 struct fs_fte *fte;
 int modify_mask = BIT(MLX5_SET_FTE_MODIFY_ENABLE_MASK_DESTINATION_LIST);
 int err = 0;

 fs_get_obj(fte, rule->node.parent);

 pending = rule_is_pending(fte, rule);
 if (pending)
  act_dests = &fte->dup->act_dests;
 else
  act_dests = &fte->act_dests;

 if (!(act_dests->action.action & MLX5_FLOW_CONTEXT_ACTION_FWD_DEST))
  return -EINVAL;
 down_write_ref_node(&fte->node, false);
 fs_get_obj(fg, fte->node.parent);
 fs_get_obj(ft, fg->node.parent);

 memcpy(&rule->dest_attr, dest, sizeof(*dest));
 root = find_root(&ft->node);
 if (!pending)
  err = root->cmds->update_fte(root, ft, fg,
          modify_mask, fte);
 up_write_ref_node(&fte->node, false);

 return err;
}

int mlx5_modify_rule_destination(struct mlx5_flow_handle *handle,
     struct mlx5_flow_destination *new_dest,
     struct mlx5_flow_destination *old_dest)
{
 int i;

 if (!old_dest) {
  if (handle->num_rules != 1)
   return -EINVAL;
  return _mlx5_modify_rule_destination(handle->rule[0],
           new_dest);
 }

 for (i = 0; i < handle->num_rules; i++) {
  if (mlx5_flow_dests_cmp(old_dest, &handle->rule[i]->dest_attr))
   return _mlx5_modify_rule_destination(handle->rule[i],
            new_dest);
 }

 return -EINVAL;
}

/* Modify/set FWD rules that point on old_next_ft to point on new_next_ft  */
static int connect_fwd_rules(struct mlx5_core_dev *dev,
        struct mlx5_flow_table *new_next_ft,
        struct mlx5_flow_table *old_next_ft)
{
 struct mlx5_flow_destination dest = {};
 struct mlx5_flow_rule *iter;
 int err = 0;

 /* new_next_ft and old_next_ft could be NULL only
 * when we create/destroy the anchor flow table.
 */

 if (!new_next_ft || !old_next_ft)
  return 0;

 dest.type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE;
 dest.ft = new_next_ft;

 mutex_lock(&old_next_ft->lock);
 list_splice_init(&old_next_ft->fwd_rules, &new_next_ft->fwd_rules);
 mutex_unlock(&old_next_ft->lock);
 list_for_each_entry(iter, &new_next_ft->fwd_rules, next_ft) {
  if ((iter->sw_action & MLX5_FLOW_CONTEXT_ACTION_FWD_NEXT_NS) &&
      iter->ft->ns == new_next_ft->ns)
   continue;

  err = _mlx5_modify_rule_destination(iter, &dest);
  if (err)
   pr_err("mlx5_core: failed to modify rule to point on flow table %d\n",
          new_next_ft->id);
 }
 return 0;
}

static int connect_flow_table(struct mlx5_core_dev *dev, struct mlx5_flow_table *ft,
         struct fs_prio *prio)
{
 struct mlx5_flow_table *next_ft, *first_ft;
 int err = 0;

 /* Connect_prev_fts and update_root_ft_create are mutually exclusive */

 first_ft = list_first_entry_or_null(&prio->node.children,
         struct mlx5_flow_table, node.list);
 if (!first_ft || first_ft->level > ft->level) {
  err = connect_prev_fts(dev, ft, prio);
  if (err)
   return err;

  next_ft = first_ft ? first_ft : find_next_chained_ft(&prio->node);
  err = connect_fwd_rules(dev, ft, next_ft);
  if (err)
   return err;
 }

 if (MLX5_CAP_FLOWTABLE(dev,
          flow_table_properties_nic_receive.modify_root))
  err = update_root_ft_create(ft, prio);
 return err;
}

static void list_add_flow_table(struct mlx5_flow_table *ft,
    struct fs_prio *prio)
{
 struct list_head *prev = &prio->node.children;
 struct mlx5_flow_table *iter;

 fs_for_each_ft(iter, prio) {
  if (iter->level > ft->level)
   break;
  prev = &iter->node.list;
 }
 list_add(&ft->node.list, prev);
}

static struct mlx5_flow_table *__mlx5_create_flow_table(struct mlx5_flow_namespace *ns,
       struct mlx5_flow_table_attr *ft_attr,
       enum fs_flow_table_op_mod op_mod,
       u16 vport)
{
 struct mlx5_flow_root_namespace *root = find_root(&ns->node);
 bool unmanaged = ft_attr->flags & MLX5_FLOW_TABLE_UNMANAGED;
 struct mlx5_flow_table *next_ft;
 struct fs_prio *fs_prio = NULL;
 struct mlx5_flow_table *ft;
 int err;

 if (!root) {
  pr_err("mlx5: flow steering failed to find root of namespace\n");
  return ERR_PTR(-ENODEV);
 }

 mutex_lock(&root->chain_lock);
 fs_prio = find_prio(ns, ft_attr->prio);
 if (!fs_prio) {
  err = -EINVAL;
  goto unlock_root;
 }
 if (!unmanaged) {
  /* The level is related to the
 * priority level range.
 */

  if (ft_attr->level >= fs_prio->num_levels) {
   err = -ENOSPC;
   goto unlock_root;
  }

  ft_attr->level += fs_prio->start_level;
 }

 /* The level is related to the
 * priority level range.
 */

 ft = alloc_flow_table(ft_attr->level,
         vport,
         root->table_type,
         op_mod, ft_attr->flags);
 if (IS_ERR(ft)) {
  err = PTR_ERR(ft);
  goto unlock_root;
 }

 tree_init_node(&ft->node, del_hw_flow_table, del_sw_flow_table);
 next_ft = unmanaged ? ft_attr->next_ft :
         find_next_chained_ft(&fs_prio->node);
 ft->def_miss_action = ns->def_miss_action;
 ft->ns = ns;
 err = root->cmds->create_flow_table(root, ft, ft_attr, next_ft);
 if (err)
  goto free_ft;

 if (!unmanaged) {
  err = connect_flow_table(root->dev, ft, fs_prio);
  if (err)
   goto destroy_ft;
 }

 ft->node.active = true;
 down_write_ref_node(&fs_prio->node, false);
 if (!unmanaged) {
  tree_add_node(&ft->node, &fs_prio->node);
  list_add_flow_table(ft, fs_prio);
 } else {
  ft->node.root = fs_prio->node.root;
 }
 fs_prio->num_ft++;
 up_write_ref_node(&fs_prio->node, false);
 mutex_unlock(&root->chain_lock);
 trace_mlx5_fs_add_ft(ft);
 return ft;
destroy_ft:
 root->cmds->destroy_flow_table(root, ft);
free_ft:
 rhltable_destroy(&ft->fgs_hash);
 kfree(ft);
unlock_root:
 mutex_unlock(&root->chain_lock);
 return ERR_PTR(err);
}

struct mlx5_flow_table *mlx5_create_flow_table(struct mlx5_flow_namespace *ns,
            struct mlx5_flow_table_attr *ft_attr)
{
 return __mlx5_create_flow_table(ns, ft_attr, FS_FT_OP_MOD_NORMAL, 0);
}
EXPORT_SYMBOL(mlx5_create_flow_table);

u32 mlx5_flow_table_id(struct mlx5_flow_table *ft)
{
 return ft->id;
}
EXPORT_SYMBOL(mlx5_flow_table_id);

struct mlx5_flow_table *
mlx5_create_vport_flow_table(struct mlx5_flow_namespace *ns,
        struct mlx5_flow_table_attr *ft_attr, u16 vport)
{
 return __mlx5_create_flow_table(ns, ft_attr, FS_FT_OP_MOD_NORMAL, vport);
}

struct mlx5_flow_table*
mlx5_create_lag_demux_flow_table(struct mlx5_flow_namespace *ns,
     int prio, u32 level)
{
 struct mlx5_flow_table_attr ft_attr = {};

 ft_attr.level = level;
 ft_attr.prio  = prio;
 ft_attr.max_fte = 1;

 return __mlx5_create_flow_table(ns, &ft_attr, FS_FT_OP_MOD_LAG_DEMUX, 0);
}
EXPORT_SYMBOL(mlx5_create_lag_demux_flow_table);

#define MAX_FLOW_GROUP_SIZE BIT(24)
struct mlx5_flow_table*
mlx5_create_auto_grouped_flow_table(struct mlx5_flow_namespace *ns,
        struct mlx5_flow_table_attr *ft_attr)
{
 int num_reserved_entries = ft_attr->autogroup.num_reserved_entries;
 int max_num_groups = ft_attr->autogroup.max_num_groups;
 struct mlx5_flow_table *ft;
 int autogroups_max_fte;

 ft = mlx5_create_vport_flow_table(ns, ft_attr, ft_attr->vport);
 if (IS_ERR(ft))
  return ft;

 autogroups_max_fte = ft->max_fte - num_reserved_entries;
 if (max_num_groups > autogroups_max_fte)
  goto err_validate;
 if (num_reserved_entries > ft->max_fte)
  goto err_validate;

 /* Align the number of groups according to the largest group size */
 if (autogroups_max_fte / (max_num_groups + 1) > MAX_FLOW_GROUP_SIZE)
  max_num_groups = (autogroups_max_fte / MAX_FLOW_GROUP_SIZE) - 1;

 ft->autogroup.active = true;
 ft->autogroup.required_groups = max_num_groups;
 ft->autogroup.max_fte = autogroups_max_fte;
 /* We save place for flow groups in addition to max types */
 ft->autogroup.group_size = autogroups_max_fte / (max_num_groups + 1);

 return ft;

err_validate:
 mlx5_destroy_flow_table(ft);
 return ERR_PTR(-ENOSPC);
}
EXPORT_SYMBOL(mlx5_create_auto_grouped_flow_table);

struct mlx5_flow_group *mlx5_create_flow_group(struct mlx5_flow_table *ft,
            u32 *fg_in)
{
 struct mlx5_flow_root_namespace *root = find_root(&ft->node);
 void *match_criteria = MLX5_ADDR_OF(create_flow_group_in,
         fg_in, match_criteria);
 u8 match_criteria_enable = MLX5_GET(create_flow_group_in,
         fg_in,
         match_criteria_enable);
 int start_index = MLX5_GET(create_flow_group_in, fg_in,
       start_flow_index);
 int end_index = MLX5_GET(create_flow_group_in, fg_in,
     end_flow_index);
 struct mlx5_flow_group *fg;
 int err;

 if (ft->autogroup.active && start_index < ft->autogroup.max_fte)
  return ERR_PTR(-EPERM);

 down_write_ref_node(&ft->node, false);
 fg = alloc_insert_flow_group(ft, match_criteria_enable, match_criteria,
         start_index, end_index,
         ft->node.children.prev);
 up_write_ref_node(&ft->node, false);
 if (IS_ERR(fg))
  return fg;

 err = root->cmds->create_flow_group(root, ft, fg_in, fg);
 if (err) {
  tree_put_node(&fg->node, false);
  return ERR_PTR(err);
 }
 trace_mlx5_fs_add_fg(fg);
 fg->node.active = true;

 return fg;
}
EXPORT_SYMBOL(mlx5_create_flow_group);

static struct mlx5_flow_rule *alloc_rule(struct mlx5_flow_destination *dest)
{
 struct mlx5_flow_rule *rule;

 rule = kzalloc(sizeof(*rule), GFP_KERNEL);
 if (!rule)
  return NULL;

 INIT_LIST_HEAD(&rule->next_ft);
 rule->node.type = FS_TYPE_FLOW_DEST;
 if (dest)
  memcpy(&rule->dest_attr, dest, sizeof(*dest));
 else
  rule->dest_attr.type = MLX5_FLOW_DESTINATION_TYPE_NONE;

 return rule;
}

static struct mlx5_flow_handle *alloc_handle(int num_rules)
{
 struct mlx5_flow_handle *handle;

 handle = kzalloc(struct_size(handle, rule, num_rules), GFP_KERNEL);
 if (!handle)
  return NULL;

 handle->num_rules = num_rules;

 return handle;
}

static void destroy_flow_handle_dup(struct mlx5_flow_handle *handle,
        int i)
{
 for (; --i >= 0;) {
  list_del(&handle->rule[i]->node.list);
  kfree(handle->rule[i]);
 }
 kfree(handle);
}

static void destroy_flow_handle(struct fs_fte *fte,
    struct mlx5_flow_handle *handle,
    struct mlx5_flow_destination *dest,
    int i)
{
 for (; --i >= 0;) {
  if (refcount_dec_and_test(&handle->rule[i]->node.refcount)) {
   fte->act_dests.dests_size--;
   list_del(&handle->rule[i]->node.list);
   kfree(handle->rule[i]);
  }
 }
 kfree(handle);
}

static struct mlx5_flow_handle *
create_flow_handle_dup(struct list_head *children,
         struct mlx5_flow_destination *dest,
         int dest_num,
         struct fs_fte_action *act_dests)
{
 static int dst = BIT(MLX5_SET_FTE_MODIFY_ENABLE_MASK_DESTINATION_LIST);
 static int count = BIT(MLX5_SET_FTE_MODIFY_ENABLE_MASK_FLOW_COUNTERS);
 struct mlx5_flow_rule *rule = NULL;
 struct mlx5_flow_handle *handle;
 int i = 0;
 int type;

 handle = alloc_handle((dest_num) ? dest_num : 1);
 if (!handle)
  return NULL;

 do {
  rule = alloc_rule(dest + i);
  if (!rule)
   goto free_rules;

  /* Add dest to dests list- we need flow tables to be in the
 * end of the list for forward to next prio rules.
 */

  tree_init_node(&rule->node, NULL, del_sw_hw_dup_rule);
  if (dest &&
      dest[i].type != MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE)
   list_add(&rule->node.list, children);
  else
   list_add_tail(&rule->node.list, children);

  if (dest) {
   act_dests->dests_size++;

   if (is_fwd_dest_type(dest[i].type))
    act_dests->fwd_dests++;

   type = dest[i].type ==
    MLX5_FLOW_DESTINATION_TYPE_COUNTER;
   act_dests->modify_mask |= type ? count : dst;
  }
  handle->rule[i] = rule;
 } while (++i < dest_num);

 return handle;

free_rules:
 destroy_flow_handle_dup(handle, i);
 act_dests->dests_size = 0;
 act_dests->fwd_dests = 0;

 return NULL;
}

static struct mlx5_flow_handle *
create_flow_handle(struct fs_fte *fte,
     struct mlx5_flow_destination *dest,
     int dest_num,
     int *modify_mask,
     bool *new_rule)
{
 struct mlx5_flow_handle *handle;
 struct mlx5_flow_rule *rule = NULL;
 static int count = BIT(MLX5_SET_FTE_MODIFY_ENABLE_MASK_FLOW_COUNTERS);
 static int dst = BIT(MLX5_SET_FTE_MODIFY_ENABLE_MASK_DESTINATION_LIST);
 int type;
 int i = 0;

 handle = alloc_handle((dest_num) ? dest_num : 1);
 if (!handle)
  return ERR_PTR(-ENOMEM);

 do {
  if (dest) {
   rule = find_flow_rule(fte, dest + i);
   if (rule) {
    refcount_inc(&rule->node.refcount);
    goto rule_found;
   }
  }

  *new_rule = true;
  rule = alloc_rule(dest + i);
  if (!rule)
   goto free_rules;

  /* Add dest to dests list- we need flow tables to be in the
 * end of the list for forward to next prio rules.
 */

  tree_init_node(&rule->node, NULL, del_sw_hw_rule);
  if (dest &&
      dest[i].type != MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE)
   list_add(&rule->node.list, &fte->node.children);
  else
   list_add_tail(&rule->node.list, &fte->node.children);
  if (dest) {
   fte->act_dests.dests_size++;

   if (is_fwd_dest_type(dest[i].type))
    fte->act_dests.fwd_dests++;

   type = dest[i].type ==
    MLX5_FLOW_DESTINATION_TYPE_COUNTER;
   *modify_mask |= type ? count : dst;
  }
rule_found:
  handle->rule[i] = rule;
 } while (++i < dest_num);

 return handle;

free_rules:
 destroy_flow_handle(fte, handle, dest, i);
 return ERR_PTR(-ENOMEM);
}

/* fte should not be deleted while calling this function */
static struct mlx5_flow_handle *
add_rule_fte(struct fs_fte *fte,
      struct mlx5_flow_group *fg,
      struct mlx5_flow_destination *dest,
      int dest_num,
      bool update_action)
{
 struct mlx5_flow_root_namespace *root;
 struct mlx5_flow_handle *handle;
 struct mlx5_flow_table *ft;
 int modify_mask = 0;
 int err;
 bool new_rule = false;

 handle = create_flow_handle(fte, dest, dest_num, &modify_mask,
        &new_rule);
 if (IS_ERR(handle) || !new_rule)
  goto out;

 if (update_action)
  modify_mask |= BIT(MLX5_SET_FTE_MODIFY_ENABLE_MASK_ACTION);

 fs_get_obj(ft, fg->node.parent);
 root = find_root(&fg->node);
 if (!(fte->status & FS_FTE_STATUS_EXISTING))
  err = root->cmds->create_fte(root, ft, fg, fte);
 else
  err = root->cmds->update_fte(root, ft, fg, modify_mask, fte);
 if (err)
  goto free_handle;

 fte->node.active = true;
 fte->status |= FS_FTE_STATUS_EXISTING;
 atomic_inc(&fg->node.version);

out:
 return handle;

free_handle:
 destroy_flow_handle(fte, handle, dest, handle->num_rules);
 return ERR_PTR(err);
}

static struct mlx5_flow_group *alloc_auto_flow_group(struct mlx5_flow_table  *ft,
           const struct mlx5_flow_spec *spec)
{
 struct list_head *prev = &ft->node.children;
 u32 max_fte = ft->autogroup.max_fte;
 unsigned int candidate_index = 0;
 unsigned int group_size = 0;
 struct mlx5_flow_group *fg;

 if (!ft->autogroup.active)
  return ERR_PTR(-ENOENT);

 if (ft->autogroup.num_groups < ft->autogroup.required_groups)
  group_size = ft->autogroup.group_size;

 /*  max_fte == ft->autogroup.max_types */
 if (group_size == 0)
  group_size = 1;

 /* sorted by start_index */
 fs_for_each_fg(fg, ft) {
  if (candidate_index + group_size > fg->start_index)
   candidate_index = fg->start_index + fg->max_ftes;
  else
   break;
  prev = &fg->node.list;
 }

 if (candidate_index + group_size > max_fte)
  return ERR_PTR(-ENOSPC);

 fg = alloc_insert_flow_group(ft,
         spec->match_criteria_enable,
         spec->match_criteria,
         candidate_index,
         candidate_index + group_size - 1,
         prev);
 if (IS_ERR(fg))
  goto out;

 if (group_size == ft->autogroup.group_size)
  ft->autogroup.num_groups++;

out:
 return fg;
}

static int create_auto_flow_group(struct mlx5_flow_table *ft,
      struct mlx5_flow_group *fg)
{
 struct mlx5_flow_root_namespace *root = find_root(&ft->node);
 int inlen = MLX5_ST_SZ_BYTES(create_flow_group_in);
 void *match_criteria_addr;
 u8 src_esw_owner_mask_on;
 void *misc;
 int err;
 u32 *in;

 in = kvzalloc(inlen, GFP_KERNEL);
 if (!in)
  return -ENOMEM;

 MLX5_SET(create_flow_group_in, in, match_criteria_enable,
   fg->mask.match_criteria_enable);
 MLX5_SET(create_flow_group_in, in, start_flow_index, fg->start_index);
 MLX5_SET(create_flow_group_in, in, end_flow_index,   fg->start_index +
   fg->max_ftes - 1);

 misc = MLX5_ADDR_OF(fte_match_param, fg->mask.match_criteria,
       misc_parameters);
 src_esw_owner_mask_on = !!MLX5_GET(fte_match_set_misc, misc,
      source_eswitch_owner_vhca_id);
 MLX5_SET(create_flow_group_in, in,
   source_eswitch_owner_vhca_id_valid, src_esw_owner_mask_on);

 match_criteria_addr = MLX5_ADDR_OF(create_flow_group_in,
        in, match_criteria);
 memcpy(match_criteria_addr, fg->mask.match_criteria,
        sizeof(fg->mask.match_criteria));

 err = root->cmds->create_flow_group(root, ft, in, fg);
 if (!err) {
  fg->node.active = true;
  trace_mlx5_fs_add_fg(fg);
 }

 kvfree(in);
 return err;
}

int mlx5_fs_get_packet_reformat_id(struct mlx5_pkt_reformat *pkt_reformat,
       u32 *id)
{
 switch (pkt_reformat->owner) {
 case MLX5_FLOW_RESOURCE_OWNER_FW:
  *id = pkt_reformat->id;
  return 0;
 case MLX5_FLOW_RESOURCE_OWNER_SW:
  return mlx5_fs_dr_action_get_pkt_reformat_id(pkt_reformat, id);
 case MLX5_FLOW_RESOURCE_OWNER_HWS:
  return mlx5_fs_hws_action_get_pkt_reformat_id(pkt_reformat, id);
 default:
  return -EINVAL;
 }
}

static bool mlx5_pkt_reformat_cmp(struct mlx5_pkt_reformat *p1,
      struct mlx5_pkt_reformat *p2)
{
 int err1, err2;
 u32 id1, id2;

 if (p1->owner != p2->owner)
  return false;

 err1 = mlx5_fs_get_packet_reformat_id(p1, &id1);
 err2 = mlx5_fs_get_packet_reformat_id(p2, &id2);

 return !err1 && !err2 && id1 == id2;
}

static bool mlx5_flow_dests_cmp(struct mlx5_flow_destination *d1,
    struct mlx5_flow_destination *d2)
{
 if (d1->type == d2->type) {
  if (((d1->type == MLX5_FLOW_DESTINATION_TYPE_VPORT ||
        d1->type == MLX5_FLOW_DESTINATION_TYPE_UPLINK) &&
       d1->vport.num == d2->vport.num &&
       d1->vport.flags == d2->vport.flags &&
       ((d1->vport.flags & MLX5_FLOW_DEST_VPORT_VHCA_ID) ?
        (d1->vport.vhca_id == d2->vport.vhca_id) : true) &&
       ((d1->vport.flags & MLX5_FLOW_DEST_VPORT_REFORMAT_ID) ?
        mlx5_pkt_reformat_cmp(d1->vport.pkt_reformat,
         d2->vport.pkt_reformat) : true)) ||
      (d1->type == MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE &&
       d1->ft == d2->ft) ||
      (d1->type == MLX5_FLOW_DESTINATION_TYPE_TIR &&
       d1->tir_num == d2->tir_num) ||
      (d1->type == MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE_NUM &&
       d1->ft_num == d2->ft_num) ||
      (d1->type == MLX5_FLOW_DESTINATION_TYPE_FLOW_SAMPLER &&
       d1->sampler_id == d2->sampler_id) ||
      (d1->type == MLX5_FLOW_DESTINATION_TYPE_RANGE &&
       d1->range.field == d2->range.field &&
       d1->range.hit_ft == d2->range.hit_ft &&
       d1->range.miss_ft == d2->range.miss_ft &&
       d1->range.min == d2->range.min &&
       d1->range.max == d2->range.max))
   return true;
 }

 return false;
}

static struct mlx5_flow_rule *find_flow_rule(struct fs_fte *fte,
          struct mlx5_flow_destination *dest)
{
 struct mlx5_flow_rule *rule;

 list_for_each_entry(rule, &fte->node.children, node.list) {
  if (mlx5_flow_dests_cmp(&rule->dest_attr, dest))
   return rule;
 }
 return NULL;
}

static bool check_conflicting_actions_vlan(const struct mlx5_fs_vlan *vlan0,
        const struct mlx5_fs_vlan *vlan1)
{
 return vlan0->ethtype != vlan1->ethtype ||
        vlan0->vid != vlan1->vid ||
        vlan0->prio != vlan1->prio;
}

static bool check_conflicting_actions(const struct mlx5_flow_act *act1,
          const struct mlx5_flow_act *act2)
{
 u32 action1 = act1->action;
 u32 action2 = act2->action;
 u32 xored_actions;

 xored_actions = action1 ^ action2;

 /* if one rule only wants to count, it's ok */
 if (action1 == MLX5_FLOW_CONTEXT_ACTION_COUNT ||
     action2 == MLX5_FLOW_CONTEXT_ACTION_COUNT)
  return false;

 if (xored_actions & (MLX5_FLOW_CONTEXT_ACTION_DROP  |
        MLX5_FLOW_CONTEXT_ACTION_PACKET_REFORMAT |
        MLX5_FLOW_CONTEXT_ACTION_DECAP |
        MLX5_FLOW_CONTEXT_ACTION_MOD_HDR  |
        MLX5_FLOW_CONTEXT_ACTION_VLAN_POP |
        MLX5_FLOW_CONTEXT_ACTION_VLAN_PUSH |
        MLX5_FLOW_CONTEXT_ACTION_VLAN_POP_2 |
        MLX5_FLOW_CONTEXT_ACTION_VLAN_PUSH_2))
  return true;

 if (action1 & MLX5_FLOW_CONTEXT_ACTION_PACKET_REFORMAT &&
     act1->pkt_reformat != act2->pkt_reformat)
  return true;

 if (action1 & MLX5_FLOW_CONTEXT_ACTION_MOD_HDR &&
     act1->modify_hdr != act2->modify_hdr)
  return true;

 if (action1 & MLX5_FLOW_CONTEXT_ACTION_VLAN_PUSH &&
     check_conflicting_actions_vlan(&act1->vlan[0], &act2->vlan[0]))
  return true;

 if (action1 & MLX5_FLOW_CONTEXT_ACTION_VLAN_PUSH_2 &&
     check_conflicting_actions_vlan(&act1->vlan[1], &act2->vlan[1]))
  return true;

 return false;
}

static int check_conflicting_ftes(struct fs_fte *fte,
      const struct mlx5_flow_context *flow_context,
      const struct mlx5_flow_act *flow_act)
{
 if (check_conflicting_actions(flow_act, &fte->act_dests.action)) {
  mlx5_core_warn(get_dev(&fte->node),
          "Found two FTEs with conflicting actions\n");
  return -EEXIST;
 }

 if ((flow_context->flags & FLOW_CONTEXT_HAS_TAG) &&
     fte->act_dests.flow_context.flow_tag != flow_context->flow_tag) {
  mlx5_core_warn(get_dev(&fte->node),
          "FTE flow tag %u already exists with different flow tag %u\n",
          fte->act_dests.flow_context.flow_tag,
          flow_context->flow_tag);
  return -EEXIST;
 }

 return 0;
}

static struct mlx5_flow_handle *add_rule_fg(struct mlx5_flow_group *fg,
         const struct mlx5_flow_spec *spec,
         struct mlx5_flow_act *flow_act,
         struct mlx5_flow_destination *dest,
         int dest_num,
         struct fs_fte *fte)
{
 struct mlx5_flow_handle *handle;
 int old_action;
 int i;
 int ret;

 ret = check_conflicting_ftes(fte, &spec->flow_context, flow_act);
 if (ret)
  return ERR_PTR(ret);

 old_action = fte->act_dests.action.action;
 fte->act_dests.action.action |= flow_act->action;
 handle = add_rule_fte(fte, fg, dest, dest_num,
         old_action != flow_act->action);
 if (IS_ERR(handle)) {
  fte->act_dests.action.action = old_action;
  return handle;
 }
 trace_mlx5_fs_set_fte(fte, false);

 /* Link newly added rules into the tree. */
 for (i = 0; i < handle->num_rules; i++) {
  if (!handle->rule[i]->node.parent) {
   tree_add_node(&handle->rule[i]->node, &fte->node);
   trace_mlx5_fs_add_rule(handle->rule[i]);
  }
 }
 return handle;
}

static bool counter_is_valid(u32 action)
{
 return (action & (MLX5_FLOW_CONTEXT_ACTION_DROP |
     MLX5_FLOW_CONTEXT_ACTION_ALLOW |
     MLX5_FLOW_CONTEXT_ACTION_FWD_DEST));
}

static bool dest_is_valid(struct mlx5_flow_destination *dest,
     struct mlx5_flow_act *flow_act,
     struct mlx5_flow_table *ft)
{
 bool ignore_level = flow_act->flags & FLOW_ACT_IGNORE_FLOW_LEVEL;
 u32 action = flow_act->action;

 if (dest && (dest->type == MLX5_FLOW_DESTINATION_TYPE_COUNTER))
  return counter_is_valid(action);

 if (!(action & MLX5_FLOW_CONTEXT_ACTION_FWD_DEST))
  return true;

 if (ignore_level) {
  if (ft->type != FS_FT_FDB &&
      ft->type != FS_FT_NIC_RX &&
      ft->type != FS_FT_NIC_TX)
   return false;

  if (dest->type == MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE &&
      ft->type != dest->ft->type)
   return false;
 }

 if (!dest || ((dest->type ==
     MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE) &&
     (dest->ft->level <= ft->level && !ignore_level)))
  return false;
 return true;
}

struct match_list {
 struct list_head list;
 struct mlx5_flow_group *g;
};

static void free_match_list(struct match_list *head, bool ft_locked)
{
 struct match_list *iter, *match_tmp;

 list_for_each_entry_safe(iter, match_tmp, &head->list,
     list) {
  tree_put_node(&iter->g->node, ft_locked);
  list_del(&iter->list);
  kfree(iter);
 }
}

static int build_match_list(struct match_list *match_head,
       struct mlx5_flow_table *ft,
       const struct mlx5_flow_spec *spec,
       struct mlx5_flow_group *fg,
       bool ft_locked)
{
 struct rhlist_head *tmp, *list;
 struct mlx5_flow_group *g;

 rcu_read_lock();
 INIT_LIST_HEAD(&match_head->list);
 /* Collect all fgs which has a matching match_criteria */
 list = rhltable_lookup(&ft->fgs_hash, spec, rhash_fg);
 /* RCU is atomic, we can't execute FW commands here */
 rhl_for_each_entry_rcu(g, tmp, list, hash) {
  struct match_list *curr_match;

  if (fg && fg != g)
   continue;

  if (unlikely(!tree_get_node(&g->node)))
   continue;

  curr_match = kmalloc(sizeof(*curr_match), GFP_ATOMIC);
  if (!curr_match) {
   rcu_read_unlock();
   free_match_list(match_head, ft_locked);
   return -ENOMEM;
  }
  curr_match->g = g;
  list_add_tail(&curr_match->list, &match_head->list);
 }
 rcu_read_unlock();
 return 0;
}

static u64 matched_fgs_get_version(struct list_head *match_head)
{
 struct match_list *iter;
 u64 version = 0;

 list_for_each_entry(iter, match_head, list)
  version += (u64)atomic_read(&iter->g->node.version);
 return version;
}

static struct fs_fte *
lookup_fte_locked(struct mlx5_flow_group *g,
    const u32 *match_value,
    bool take_write)
{
 struct fs_fte *fte_tmp;

 if (take_write)
  nested_down_write_ref_node(&g->node, FS_LOCK_PARENT);
 else
  nested_down_read_ref_node(&g->node, FS_LOCK_PARENT);
 fte_tmp = rhashtable_lookup_fast(&g->ftes_hash, match_value,
      rhash_fte);
 if (!fte_tmp || !tree_get_node(&fte_tmp->node)) {
  fte_tmp = NULL;
  goto out;
 }

 nested_down_write_ref_node(&fte_tmp->node, FS_LOCK_CHILD);

 if (!fte_tmp->node.active) {
  up_write_ref_node(&fte_tmp->node, false);

  if (take_write)
   up_write_ref_node(&g->node, false);
  else
   up_read_ref_node(&g->node);

  tree_put_node(&fte_tmp->node, false);

  return NULL;
 }

out:
 if (take_write)
  up_write_ref_node(&g->node, false);
 else
  up_read_ref_node(&g->node);
 return fte_tmp;
}

/* Native capability lacks support for adding an additional match with the same value
 * to the same flow group. To accommodate the NO APPEND flag in these scenarios,
 * we include the new rule in the existing flow table entry (fte) without immediate
 * hardware commitment. When a request is made to delete the corresponding hardware rule,
 * we then commit the pending rule to hardware.
 */

static struct mlx5_flow_handle *
add_rule_dup_match_fte(struct fs_fte *fte,
         const struct mlx5_flow_spec *spec,
         struct mlx5_flow_act *flow_act,
         struct mlx5_flow_destination *dest,
         int dest_num)
{
 struct mlx5_flow_handle *handle;
 struct fs_fte_dup *dup;
 int i = 0;

 if (!fte->dup) {
  dup = kvzalloc(sizeof(*dup), GFP_KERNEL);
  if (!dup)
   return ERR_PTR(-ENOMEM);
  /* dup will be freed when the fte is freed
 * this way we don't allocate / free dup on every rule deletion
 * or creation
 */

  INIT_LIST_HEAD(&dup->children);
  fte->dup = dup;
 }

 if (!list_empty(&fte->dup->children)) {
  mlx5_core_warn(get_dev(&fte->node),
          "Can have only a single duplicate rule\n");

  return ERR_PTR(-EEXIST);
 }

 fte->dup->act_dests.action = *flow_act;
 fte->dup->act_dests.flow_context = spec->flow_context;
 fte->dup->act_dests.dests_size = 0;
 fte->dup->act_dests.fwd_dests = 0;
 fte->dup->act_dests.modify_mask = BIT(MLX5_SET_FTE_MODIFY_ENABLE_MASK_ACTION);

 handle = create_flow_handle_dup(&fte->dup->children,
     dest, dest_num,
     &fte->dup->act_dests);
 if (!handle)
  return ERR_PTR(-ENOMEM);

 for (i = 0; i < handle->num_rules; i++) {
  tree_add_node(&handle->rule[i]->node, &fte->node);
  trace_mlx5_fs_add_rule(handle->rule[i]);
 }

 return handle;
}

static struct mlx5_flow_handle *
try_add_to_existing_fg(struct mlx5_flow_table *ft,
         struct list_head *match_head,
         const struct mlx5_flow_spec *spec,
         struct mlx5_flow_act *flow_act,
         struct mlx5_flow_destination *dest,
         int dest_num,
         int ft_version)
{
 struct mlx5_flow_steering *steering = get_steering(&ft->node);
 struct mlx5_flow_root_namespace *root = find_root(&ft->node);
 struct mlx5_flow_group *g;
 struct mlx5_flow_handle *rule;
 struct match_list *iter;
 bool take_write = false;
 bool try_again = false;
 struct fs_fte *fte;
 u64  version = 0;
 int err;

 fte = alloc_fte(ft, spec, flow_act);
 if (IS_ERR(fte))
  return  ERR_PTR(-ENOMEM);

search_again_locked:
 if (flow_act->flags & FLOW_ACT_NO_APPEND &&
     (root->cmds->get_capabilities(root, root->table_type) &
      MLX5_FLOW_STEERING_CAP_DUPLICATE_MATCH))
  goto skip_search;
 version = matched_fgs_get_version(match_head);
 /* Try to find an fte with identical match value and attempt update its
 * action.
 */

 list_for_each_entry(iter, match_head, list) {
  struct fs_fte *fte_tmp;

  g = iter->g;
  fte_tmp = lookup_fte_locked(g, spec->match_value, take_write);
  if (!fte_tmp)
   continue;
  if (flow_act->flags & FLOW_ACT_NO_APPEND)
   rule = add_rule_dup_match_fte(fte_tmp, spec, flow_act, dest, dest_num);
  else
   rule = add_rule_fg(g, spec, flow_act, dest, dest_num, fte_tmp);
  /* No error check needed here, because insert_fte() is not called */
  up_write_ref_node(&fte_tmp->node, false);
  tree_put_node(&fte_tmp->node, false);
  kmem_cache_free(steering->ftes_cache, fte);
  return rule;
 }

skip_search:
 /* No group with matching fte found, or we skipped the search.
 * Try to add a new fte to any matching fg.
 */


 /* Check the ft version, for case that new flow group
 * was added while the fgs weren't locked
 */

 if (atomic_read(&ft->node.version) != ft_version) {
  rule = ERR_PTR(-EAGAIN);
  goto out;
 }

 /* Check the fgs version. If version have changed it could be that an
 * FTE with the same match value was added while the fgs weren't
 * locked.
 */

 if (!(flow_act->flags & FLOW_ACT_NO_APPEND) &&
     version != matched_fgs_get_version(match_head)) {
  take_write = true;
  goto search_again_locked;
 }

 list_for_each_entry(iter, match_head, list) {
  g = iter->g;

  nested_down_write_ref_node(&g->node, FS_LOCK_PARENT);

  if (!g->node.active) {
   try_again = true;
   up_write_ref_node(&g->node, false);
   continue;
  }

  err = insert_fte(g, fte);
  if (err) {
   up_write_ref_node(&g->node, false);
   if (err == -ENOSPC)
    continue;
   kmem_cache_free(steering->ftes_cache, fte);
   return ERR_PTR(err);
  }

  nested_down_write_ref_node(&fte->node, FS_LOCK_CHILD);
  up_write_ref_node(&g->node, false);
  rule = add_rule_fg(g, spec, flow_act, dest, dest_num, fte);
  up_write_ref_node(&fte->node, false);
  if (IS_ERR(rule))
   tree_put_node(&fte->node, false);
  return rule;
 }
 err = try_again ? -EAGAIN : -ENOENT;
 rule = ERR_PTR(err);
out:
 kmem_cache_free(steering->ftes_cache, fte);
 return rule;
}

static struct mlx5_flow_handle *
_mlx5_add_flow_rules(struct mlx5_flow_table *ft,
       const struct mlx5_flow_spec *spec,
       struct mlx5_flow_act *flow_act,
       struct mlx5_flow_destination *dest,
       int dest_num)

{
 struct mlx5_flow_steering *steering = get_steering(&ft->node);
 struct mlx5_flow_handle *rule;
 struct match_list match_head;
 struct mlx5_flow_group *g;
 bool take_write = false;
 struct fs_fte *fte;
 int version;
 int err;
 int i;

 if (!check_valid_spec(spec))
  return ERR_PTR(-EINVAL);

 if (flow_act->fg && ft->autogroup.active)
  return ERR_PTR(-EINVAL);

 if (dest && dest_num <= 0)
  return ERR_PTR(-EINVAL);

 for (i = 0; i < dest_num; i++) {
  if (!dest_is_valid(&dest[i], flow_act, ft))
   return ERR_PTR(-EINVAL);
 }
 nested_down_read_ref_node(&ft->node, FS_LOCK_GRANDPARENT);
search_again_locked:
 version = atomic_read(&ft->node.version);

 /* Collect all fgs which has a matching match_criteria */
 err = build_match_list(&match_head, ft, spec, flow_act->fg, take_write);
 if (err) {
  if (take_write)
   up_write_ref_node(&ft->node, false);
  else
   up_read_ref_node(&ft->node);
  return ERR_PTR(err);
 }

 if (!take_write)
  up_read_ref_node(&ft->node);

 rule = try_add_to_existing_fg(ft, &match_head.list, spec, flow_act, dest,
--> --------------------

--> maximum size reached

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

Messung V0.5
C=98 H=94 G=95

¤ Dauer der Verarbeitung: 0.42 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.