/*-
 * Copyright (c) 2013-2017, Mellanox Technologies, Ltd.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 *
 * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS `AS IS' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $FreeBSD$
 */

#include <linux/module.h>
#include <dev/mlx5/driver.h>
#include "mlx5_core.h"
#include "fs_core.h"
#include <linux/string.h>
#include <linux/compiler.h>

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

#define ADD_PRIO(name_val, flags_val, min_level_val, max_ft_val, caps_val, \
		 ...) {.type = FS_TYPE_PRIO,\
	.name = name_val,\
	.min_ft_level = min_level_val,\
	.flags = flags_val,\
	.max_ft = max_ft_val,\
	.caps = caps_val,\
	.children = (struct init_tree_node[]) {__VA_ARGS__},\
	.ar_size = INIT_TREE_NODE_ARRAY_SIZE(__VA_ARGS__) \
}

#define ADD_FT_PRIO(name_val, flags_val, max_ft_val,  ...)\
	ADD_PRIO(name_val, flags_val, 0, max_ft_val, {},\
		 __VA_ARGS__)\

#define ADD_NS(name_val, ...) {.type = FS_TYPE_NAMESPACE,\
	.name = name_val,\
	.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 BYPASS_MAX_FT 5
#define BYPASS_PRIO_MAX_FT 1
#define KERNEL_MAX_FT 3
#define LEFTOVER_MAX_FT 1
#define KENREL_MIN_LEVEL 3
#define LEFTOVER_MIN_LEVEL KENREL_MIN_LEVEL + 1
#define BYPASS_MIN_LEVEL MLX5_NUM_BYPASS_FTS + LEFTOVER_MIN_LEVEL
struct node_caps {
	size_t	arr_sz;
	long	*caps;
};

struct init_tree_node {
	enum fs_type	type;
	const char	*name;
	struct init_tree_node *children;
	int ar_size;
	struct node_caps caps;
	u8  flags;
	int min_ft_level;
	int prio;
	int max_ft;
} root_fs = {
	.type = FS_TYPE_NAMESPACE,
	.name = "root",
	.ar_size = 3,
	.children = (struct init_tree_node[]) {
		ADD_PRIO("by_pass_prio", 0, BYPASS_MIN_LEVEL, 0,
			 FS_REQUIRED_CAPS(FS_CAP(flow_table_properties_nic_receive.flow_modify_en),
					  FS_CAP(flow_table_properties_nic_receive.modify_root)),
			 ADD_NS("by_pass_ns",
				ADD_FT_PRIO("prio0", 0,
					    BYPASS_PRIO_MAX_FT),
				ADD_FT_PRIO("prio1", 0,
					    BYPASS_PRIO_MAX_FT),
				ADD_FT_PRIO("prio2", 0,
					    BYPASS_PRIO_MAX_FT),
				ADD_FT_PRIO("prio3", 0,
					    BYPASS_PRIO_MAX_FT),
				ADD_FT_PRIO("prio4", 0,
					    BYPASS_PRIO_MAX_FT),
				ADD_FT_PRIO("prio5", 0,
					    BYPASS_PRIO_MAX_FT),
				ADD_FT_PRIO("prio6", 0,
					    BYPASS_PRIO_MAX_FT),
				ADD_FT_PRIO("prio7", 0,
					    BYPASS_PRIO_MAX_FT),
				ADD_FT_PRIO("prio-mcast", 0,
					    BYPASS_PRIO_MAX_FT))),
		ADD_PRIO("kernel_prio", 0, KENREL_MIN_LEVEL, 0, {},
			 ADD_NS("kernel_ns",
				ADD_FT_PRIO("prio_kernel-0", 0,
					    KERNEL_MAX_FT))),
		ADD_PRIO("leftovers_prio", MLX5_CORE_FS_PRIO_SHARED,
			 LEFTOVER_MIN_LEVEL, 0,
			 FS_REQUIRED_CAPS(FS_CAP(flow_table_properties_nic_receive.flow_modify_en),
					  FS_CAP(flow_table_properties_nic_receive.modify_root)),
			 ADD_NS("leftover_ns",
				ADD_FT_PRIO("leftovers_prio-0",
					MLX5_CORE_FS_PRIO_SHARED,
					LEFTOVER_MAX_FT)))
	}
};

/* Tree creation functions */

static struct mlx5_flow_root_namespace *find_root(struct fs_base *node)
{
	struct fs_base *parent;

	/* Make sure we only read it once while we go up the tree */
	while ((parent = node->parent))
		node = parent;

	if (node->type != FS_TYPE_NAMESPACE) {
		return NULL;
	}

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

static inline struct mlx5_core_dev *fs_get_dev(struct fs_base *node)
{
	struct mlx5_flow_root_namespace *root = find_root(node);

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

static void fs_init_node(struct fs_base *node,
			 unsigned int refcount)
{
	kref_init(&node->refcount);
	atomic_set(&node->users_refcount, refcount);
	init_completion(&node->complete);
	INIT_LIST_HEAD(&node->list);
	mutex_init(&node->lock);
}

static void _fs_add_node(struct fs_base *node,
			 const char *name,
			 struct fs_base *parent)
{
	if (parent)
		atomic_inc(&parent->users_refcount);
	node->name = kstrdup_const(name, GFP_KERNEL);
	node->parent = parent;
}

static void fs_add_node(struct fs_base *node,
			struct fs_base *parent, const char *name,
			unsigned int refcount)
{
	fs_init_node(node, refcount);
	_fs_add_node(node, name, parent);
}

static void _fs_put(struct fs_base *node, void (*kref_cb)(struct kref *kref),
		    bool parent_locked);

static void fs_del_dst(struct mlx5_flow_rule *dst);
static void _fs_del_ft(struct mlx5_flow_table *ft);
static void fs_del_fg(struct mlx5_flow_group *fg);
static void fs_del_fte(struct fs_fte *fte);

static void cmd_remove_node(struct fs_base *base)
{
	switch (base->type) {
	case FS_TYPE_FLOW_DEST:
		fs_del_dst(container_of(base, struct mlx5_flow_rule, base));
		break;
	case FS_TYPE_FLOW_TABLE:
		_fs_del_ft(container_of(base, struct mlx5_flow_table, base));
		break;
	case FS_TYPE_FLOW_GROUP:
		fs_del_fg(container_of(base, struct mlx5_flow_group, base));
		break;
	case FS_TYPE_FLOW_ENTRY:
		fs_del_fte(container_of(base, struct fs_fte, base));
		break;
	default:
		break;
	}
}

static void __fs_remove_node(struct kref *kref)
{
	struct fs_base *node = container_of(kref, struct fs_base, refcount);

	if (node->parent)
		mutex_lock(&node->parent->lock);
	mutex_lock(&node->lock);
	cmd_remove_node(node);
	mutex_unlock(&node->lock);
	complete(&node->complete);
	if (node->parent) {
		mutex_unlock(&node->parent->lock);
		_fs_put(node->parent, _fs_remove_node, false);
	}
}

void _fs_remove_node(struct kref *kref)
{
	struct fs_base *node = container_of(kref, struct fs_base, refcount);

	__fs_remove_node(kref);
	kfree_const(node->name);
	kfree(node);
}

static void fs_get(struct fs_base *node)
{
	atomic_inc(&node->users_refcount);
}

static void _fs_put(struct fs_base *node, void (*kref_cb)(struct kref *kref),
		    bool parent_locked)
{
	struct fs_base *parent_node = node->parent;

	if (parent_node && !parent_locked)
		mutex_lock(&parent_node->lock);
	if (atomic_dec_and_test(&node->users_refcount)) {
		if (parent_node) {
			/*remove from parent's list*/
			list_del_init(&node->list);
			mutex_unlock(&parent_node->lock);
		}
		kref_put(&node->refcount, kref_cb);
		if (parent_node && parent_locked)
			mutex_lock(&parent_node->lock);
	} else if (parent_node && !parent_locked) {
		mutex_unlock(&parent_node->lock);
	}
}

static void fs_put(struct fs_base *node)
{
	_fs_put(node, __fs_remove_node, false);
}

static void fs_put_parent_locked(struct fs_base *node)
{
	_fs_put(node, __fs_remove_node, true);
}

static void fs_remove_node(struct fs_base *node)
{
	fs_put(node);
	wait_for_completion(&node->complete);
	kfree_const(node->name);
	kfree(node);
}

static void fs_remove_node_parent_locked(struct fs_base *node)
{
	fs_put_parent_locked(node);
	wait_for_completion(&node->complete);
	kfree_const(node->name);
	kfree(node);
}

static struct fs_fte *fs_alloc_fte(u8 action,
				   u32 flow_tag,
				   u32 *match_value,
				   unsigned int index)
{
	struct fs_fte *fte;


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

	memcpy(fte->val, match_value, sizeof(fte->val));
	fte->base.type =  FS_TYPE_FLOW_ENTRY;
	fte->dests_size = 0;
	fte->flow_tag = flow_tag;
	fte->index = index;
	INIT_LIST_HEAD(&fte->dests);
	fte->action = action;

	return fte;
}

static struct fs_fte *alloc_star_ft_entry(struct mlx5_flow_table *ft,
					  struct mlx5_flow_group *fg,
					  u32 *match_value,
					  unsigned int index)
{
	int err;
	struct fs_fte *fte;
	struct mlx5_flow_rule *dst;

	if (fg->num_ftes == fg->max_ftes)
		return ERR_PTR(-ENOSPC);

	fte = fs_alloc_fte(MLX5_FLOW_CONTEXT_ACTION_FWD_DEST,
			   MLX5_FS_DEFAULT_FLOW_TAG, match_value, index);
	if (IS_ERR(fte))
		return fte;

	/*create dst*/
	dst = kzalloc(sizeof(*dst), GFP_KERNEL);
	if (!dst) {
		err = -ENOMEM;
		goto free_fte;
	}

	fte->base.parent = &fg->base;
	fte->dests_size = 1;
	dst->dest_attr.type = MLX5_FLOW_CONTEXT_DEST_TYPE_FLOW_TABLE;
	dst->base.parent = &fte->base;
	list_add(&dst->base.list, &fte->dests);
	/* assumed that the callee creates the star rules sorted by index */
	list_add_tail(&fte->base.list, &fg->ftes);
	fg->num_ftes++;

	return fte;

free_fte:
	kfree(fte);
	return ERR_PTR(err);
}

/* assume that fte can't be changed */
static void free_star_fte_entry(struct fs_fte *fte)
{
	struct mlx5_flow_group	*fg;
	struct mlx5_flow_rule	*dst, *temp;

	fs_get_parent(fg, fte);

	list_for_each_entry_safe(dst, temp, &fte->dests, base.list) {
		fte->dests_size--;
		list_del(&dst->base.list);
		kfree(dst);
	}

	list_del(&fte->base.list);
	fg->num_ftes--;
	kfree(fte);
}

static struct mlx5_flow_group *fs_alloc_fg(u32 *create_fg_in)
{
	struct mlx5_flow_group *fg;
	void *match_criteria = MLX5_ADDR_OF(create_flow_group_in,
					    create_fg_in, match_criteria);
	u8 match_criteria_enable = MLX5_GET(create_flow_group_in,
					    create_fg_in,
					    match_criteria_enable);
	fg = kzalloc(sizeof(*fg), GFP_KERNEL);
	if (!fg)
		return ERR_PTR(-ENOMEM);

	INIT_LIST_HEAD(&fg->ftes);
	fg->mask.match_criteria_enable = match_criteria_enable;
	memcpy(&fg->mask.match_criteria, match_criteria,
	       sizeof(fg->mask.match_criteria));
	fg->base.type =  FS_TYPE_FLOW_GROUP;
	fg->start_index = MLX5_GET(create_flow_group_in, create_fg_in,
				   start_flow_index);
	fg->max_ftes = MLX5_GET(create_flow_group_in, create_fg_in,
				end_flow_index) - fg->start_index + 1;
	return fg;
}

static struct mlx5_flow_table *find_next_ft(struct fs_prio *prio);
static struct mlx5_flow_table *find_prev_ft(struct mlx5_flow_table *curr,
					    struct fs_prio *prio);

/* assumed src_ft and dst_ft can't be freed */
static int fs_set_star_rule(struct mlx5_core_dev *dev,
			    struct mlx5_flow_table *src_ft,
			    struct mlx5_flow_table *dst_ft)
{
	struct mlx5_flow_rule *src_dst;
	struct fs_fte *src_fte;
	int err = 0;
	u32 *match_value;
	int match_len = MLX5_ST_SZ_BYTES(fte_match_param);

	src_dst = list_first_entry(&src_ft->star_rule.fte->dests,
				   struct mlx5_flow_rule, base.list);
	match_value = mlx5_vzalloc(match_len);
	if (!match_value) {
		mlx5_core_warn(dev, "failed to allocate inbox\n");
		return -ENOMEM;
	}
	/*Create match context*/

	fs_get_parent(src_fte, src_dst);

	src_dst->dest_attr.ft = dst_ft;
	if (dst_ft) {
		err = mlx5_cmd_fs_set_fte(dev,
					  src_ft->vport,
					  &src_fte->status,
					  match_value, src_ft->type,
					  src_ft->id, src_fte->index,
					  src_ft->star_rule.fg->id,
					  src_fte->flow_tag,
					  src_fte->action,
					  src_fte->dests_size,
					  &src_fte->dests);
		if (err)
			goto free;

		fs_get(&dst_ft->base);
	} else {
		mlx5_cmd_fs_delete_fte(dev,
				       src_ft->vport,
				       &src_fte->status,
				       src_ft->type, src_ft->id,
				       src_fte->index);
	}

free:
	kvfree(match_value);
	return err;
}

static int connect_prev_fts(struct fs_prio *locked_prio,
			    struct fs_prio *prev_prio,
			    struct mlx5_flow_table *next_ft)
{
	struct mlx5_flow_table *iter;
	int err = 0;
	struct mlx5_core_dev *dev = fs_get_dev(&prev_prio->base);

	if (!dev)
		return -ENODEV;

	mutex_lock(&prev_prio->base.lock);
	fs_for_each_ft(iter, prev_prio) {
		struct mlx5_flow_rule *src_dst =
			list_first_entry(&iter->star_rule.fte->dests,
					 struct mlx5_flow_rule, base.list);
		struct mlx5_flow_table *prev_ft = src_dst->dest_attr.ft;

		if (prev_ft == next_ft)
			continue;

		err = fs_set_star_rule(dev, iter, next_ft);
		if (err) {
			mlx5_core_warn(dev,
			    "mlx5: flow steering can't connect prev and next\n");
			goto unlock;
		} else {
			/* Assume ft's prio is locked */
			if (prev_ft) {
				struct fs_prio *prio;

				fs_get_parent(prio, prev_ft);
				if (prio == locked_prio)
					fs_put_parent_locked(&prev_ft->base);
				else
					fs_put(&prev_ft->base);
			}
		}
	}

unlock:
	mutex_unlock(&prev_prio->base.lock);
	return 0;
}

static int create_star_rule(struct mlx5_flow_table *ft, struct fs_prio *prio)
{
	struct mlx5_flow_group *fg;
	int err;
	u32 *fg_in;
	u32 *match_value;
	struct mlx5_flow_table *next_ft;
	struct mlx5_flow_table *prev_ft;
	struct mlx5_flow_root_namespace *root = find_root(&prio->base);
	int fg_inlen = MLX5_ST_SZ_BYTES(create_flow_group_in);
	int match_len = MLX5_ST_SZ_BYTES(fte_match_param);

	fg_in = mlx5_vzalloc(fg_inlen);
	if (!fg_in) {
		mlx5_core_warn(root->dev, "failed to allocate inbox\n");
		return -ENOMEM;
	}

	match_value = mlx5_vzalloc(match_len);
	if (!match_value) {
		mlx5_core_warn(root->dev, "failed to allocate inbox\n");
		kvfree(fg_in);
		return -ENOMEM;
	}

	MLX5_SET(create_flow_group_in, fg_in, start_flow_index, ft->max_fte);
	MLX5_SET(create_flow_group_in, fg_in, end_flow_index, ft->max_fte);
	fg = fs_alloc_fg(fg_in);
	if (IS_ERR(fg)) {
		err = PTR_ERR(fg);
		goto out;
	}
	ft->star_rule.fg = fg;
	err =  mlx5_cmd_fs_create_fg(fs_get_dev(&prio->base),
				     fg_in, ft->vport, ft->type,
				     ft->id,
				     &fg->id);
	if (err)
		goto free_fg;

	ft->star_rule.fte = alloc_star_ft_entry(ft, fg,
						      match_value,
						      ft->max_fte);
	if (IS_ERR(ft->star_rule.fte))
		goto free_star_rule;

	mutex_lock(&root->fs_chain_lock);
	next_ft = find_next_ft(prio);
	err = fs_set_star_rule(root->dev, ft, next_ft);
	if (err) {
		mutex_unlock(&root->fs_chain_lock);
		goto free_star_rule;
	}
	if (next_ft) {
		struct fs_prio *parent;

		fs_get_parent(parent, next_ft);
		fs_put(&next_ft->base);
	}
	prev_ft = find_prev_ft(ft, prio);
	if (prev_ft) {
		struct fs_prio *prev_parent;

		fs_get_parent(prev_parent, prev_ft);

		err = connect_prev_fts(NULL, prev_parent, ft);
		if (err) {
			mutex_unlock(&root->fs_chain_lock);
			goto destroy_chained_star_rule;
		}
		fs_put(&prev_ft->base);
	}
	mutex_unlock(&root->fs_chain_lock);
	kvfree(fg_in);
	kvfree(match_value);

	return 0;

destroy_chained_star_rule:
	fs_set_star_rule(fs_get_dev(&prio->base), ft, NULL);
	if (next_ft)
		fs_put(&next_ft->base);
free_star_rule:
	free_star_fte_entry(ft->star_rule.fte);
	mlx5_cmd_fs_destroy_fg(fs_get_dev(&ft->base), ft->vport,
			       ft->type, ft->id,
			       fg->id);
free_fg:
	kfree(fg);
out:
	kvfree(fg_in);
	kvfree(match_value);
	return err;
}

static void destroy_star_rule(struct mlx5_flow_table *ft, struct fs_prio *prio)
{
	int err;
	struct mlx5_flow_root_namespace *root;
	struct mlx5_core_dev *dev = fs_get_dev(&prio->base);
	struct mlx5_flow_table *prev_ft, *next_ft;
	struct fs_prio *prev_prio;

	WARN_ON(!dev);

	root = find_root(&prio->base);
	if (!root)
		mlx5_core_err(dev,
		    "flow steering failed to find root of priority %s",
		    prio->base.name);

	/* In order to ensure atomic deletion, first update
	 * prev ft to point on the next ft.
	 */
	mutex_lock(&root->fs_chain_lock);
	prev_ft = find_prev_ft(ft, prio);
	next_ft = find_next_ft(prio);
	if (prev_ft) {
		fs_get_parent(prev_prio, prev_ft);
		/*Prev is connected to ft, only if ft is the first(last) in the prio*/
		err = connect_prev_fts(prio, prev_prio, next_ft);
		if (err)
			mlx5_core_warn(root->dev,
				       "flow steering can't connect prev and next of flow table\n");
		fs_put(&prev_ft->base);
	}

	err = fs_set_star_rule(root->dev, ft, NULL);
	/*One put is for fs_get in find next ft*/
	if (next_ft) {
		fs_put(&next_ft->base);
		if (!err)
			fs_put(&next_ft->base);
	}

	mutex_unlock(&root->fs_chain_lock);
	err = mlx5_cmd_fs_destroy_fg(dev, ft->vport, ft->type, ft->id,
				     ft->star_rule.fg->id);
	if (err)
		mlx5_core_warn(dev,
			       "flow steering can't destroy star entry group(index:%d) of ft:%s\n", ft->star_rule.fg->start_index,
			       ft->base.name);
	free_star_fte_entry(ft->star_rule.fte);

	kfree(ft->star_rule.fg);
	ft->star_rule.fg = NULL;
}

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 unsigned int _alloc_new_level(struct fs_prio *prio,
				     struct mlx5_flow_namespace *match);

static unsigned int __alloc_new_level(struct mlx5_flow_namespace *ns,
				      struct fs_prio *prio)
{
	unsigned int level = 0;
	struct fs_prio *p;

	if (!ns)
		return 0;

	mutex_lock(&ns->base.lock);
	fs_for_each_prio(p, ns) {
		if (p != prio)
			level += p->max_ft;
		else
			break;
	}
	mutex_unlock(&ns->base.lock);

	fs_get_parent(prio, ns);
	if (prio)
		WARN_ON(prio->base.type != FS_TYPE_PRIO);

	return level + _alloc_new_level(prio, ns);
}

/* Called under lock of priority, hence locking all upper objects */
static unsigned int _alloc_new_level(struct fs_prio *prio,
				     struct mlx5_flow_namespace *match)
{
	struct mlx5_flow_namespace *ns;
	struct fs_base *it;
	unsigned int level = 0;

	if (!prio)
		return 0;

	mutex_lock(&prio->base.lock);
	fs_for_each_ns_or_ft_reverse(it, prio) {
		if (it->type == FS_TYPE_NAMESPACE) {
			struct fs_prio *p;

			fs_get_obj(ns, it);

			if (match != ns) {
				mutex_lock(&ns->base.lock);
				fs_for_each_prio(p, ns)
					level += p->max_ft;
				mutex_unlock(&ns->base.lock);
			} else {
				break;
			}
		} else {
			struct mlx5_flow_table *ft;

			fs_get_obj(ft, it);
			mutex_unlock(&prio->base.lock);
			return level + ft->level + 1;
		}
	}

	fs_get_parent(ns, prio);
	mutex_unlock(&prio->base.lock);
	return __alloc_new_level(ns, prio) + level;
}

static unsigned int alloc_new_level(struct fs_prio *prio)
{
	return _alloc_new_level(prio, NULL);
}

static int update_root_ft_create(struct mlx5_flow_root_namespace *root,
				    struct mlx5_flow_table *ft)
{
	int err = 0;
	int min_level = INT_MAX;

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

	if (ft->level < min_level)
		err = mlx5_cmd_update_root_ft(root->dev, ft->type,
					      ft->id);
	else
		return err;

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

	return err;
}

static struct mlx5_flow_table *_create_ft_common(struct mlx5_flow_namespace *ns,
						 u16 vport,
						 struct fs_prio *fs_prio,
						 int max_fte,
						 const char *name)
{
	struct mlx5_flow_table *ft;
	int err;
	int log_table_sz;
	int ft_size;
	char gen_name[20];
	struct mlx5_flow_root_namespace *root = find_root(&ns->base);
	struct mlx5_core_dev *dev = fs_get_dev(&ns->base);

	if (!root) {
		mlx5_core_err(dev,
		    "flow steering failed to find root of namespace %s",
		    ns->base.name);
		return ERR_PTR(-ENODEV);
	}

	if (fs_prio->num_ft == fs_prio->max_ft)
		return ERR_PTR(-ENOSPC);

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

	fs_init_node(&ft->base, 1);
	INIT_LIST_HEAD(&ft->fgs);

	/* Temporarily WA until we expose the level set in the API */
	if (root->table_type == FS_FT_ESW_EGRESS_ACL ||
		root->table_type == FS_FT_ESW_INGRESS_ACL)
		ft->level = 0;
	else
		ft->level = alloc_new_level(fs_prio);

	ft->base.type = FS_TYPE_FLOW_TABLE;
	ft->vport = vport;
	ft->type = root->table_type;
	/*Two entries are reserved for star rules*/
	ft_size = roundup_pow_of_two(max_fte + 2);
	/*User isn't aware to those rules*/
	ft->max_fte = ft_size - 2;
	log_table_sz = ilog2(ft_size);
	err = mlx5_cmd_fs_create_ft(root->dev, ft->vport, ft->type,
				    ft->level, log_table_sz, &ft->id);
	if (err)
		goto free_ft;

	err = create_star_rule(ft, fs_prio);
	if (err)
		goto del_ft;

	if ((root->table_type == FS_FT_NIC_RX) && MLX5_CAP_FLOWTABLE(root->dev,
			       flow_table_properties_nic_receive.modify_root)) {
		err = update_root_ft_create(root, ft);
		if (err)
			goto destroy_star_rule;
	}

	if (!name || !strlen(name)) {
		snprintf(gen_name, 20, "flow_table_%u", ft->id);
		_fs_add_node(&ft->base, gen_name, &fs_prio->base);
	} else {
		_fs_add_node(&ft->base, name, &fs_prio->base);
	}
	list_add_tail(&ft->base.list, &fs_prio->objs);
	fs_prio->num_ft++;

	return ft;

destroy_star_rule:
	destroy_star_rule(ft, fs_prio);
del_ft:
	mlx5_cmd_fs_destroy_ft(root->dev, ft->vport, ft->type, ft->id);
free_ft:
	kfree(ft);
	return ERR_PTR(err);
}

static struct mlx5_flow_table *create_ft_common(struct mlx5_flow_namespace *ns,
						u16 vport,
						unsigned int prio,
						int max_fte,
						const char *name)
{
	struct fs_prio *fs_prio = NULL;
	fs_prio = find_prio(ns, prio);
	if (!fs_prio)
		return ERR_PTR(-EINVAL);

	return _create_ft_common(ns, vport, fs_prio, max_fte, name);
}


static struct mlx5_flow_table *find_first_ft_in_ns(struct mlx5_flow_namespace *ns,
						   struct list_head *start);

static struct mlx5_flow_table *find_first_ft_in_prio(struct fs_prio *prio,
						     struct list_head *start);

static struct mlx5_flow_table *mlx5_create_autogrouped_shared_flow_table(struct fs_prio *fs_prio)
{
	struct mlx5_flow_table *ft;

	ft = find_first_ft_in_prio(fs_prio, &fs_prio->objs);
	if (ft) {
		ft->shared_refcount++;
		return ft;
	}

	return NULL;
}

struct mlx5_flow_table *mlx5_create_auto_grouped_flow_table(struct mlx5_flow_namespace *ns,
							   int prio,
							   const char *name,
							   int num_flow_table_entries,
							   int max_num_groups)
{
	struct mlx5_flow_table *ft = NULL;
	struct fs_prio *fs_prio;
	bool is_shared_prio;

	fs_prio = find_prio(ns, prio);
	if (!fs_prio)
		return ERR_PTR(-EINVAL);

	is_shared_prio = fs_prio->flags & MLX5_CORE_FS_PRIO_SHARED;
	if (is_shared_prio) {
		mutex_lock(&fs_prio->shared_lock);
		ft = mlx5_create_autogrouped_shared_flow_table(fs_prio);
	}

	if (ft)
		goto return_ft;

	ft = create_ft_common(ns, 0, prio, num_flow_table_entries,
			      name);
	if (IS_ERR(ft))
		goto return_ft;

	ft->autogroup.active = true;
	ft->autogroup.max_types = max_num_groups;
	if (is_shared_prio)
		ft->shared_refcount = 1;

return_ft:
	if (is_shared_prio)
		mutex_unlock(&fs_prio->shared_lock);
	return ft;
}
EXPORT_SYMBOL(mlx5_create_auto_grouped_flow_table);

struct mlx5_flow_table *mlx5_create_vport_flow_table(struct mlx5_flow_namespace *ns,
						     u16 vport,
						     int prio,
						     const char *name,
						     int num_flow_table_entries)
{
	return create_ft_common(ns, vport, prio, num_flow_table_entries, name);
}
EXPORT_SYMBOL(mlx5_create_vport_flow_table);

struct mlx5_flow_table *mlx5_create_flow_table(struct mlx5_flow_namespace *ns,
					       int prio,
					       const char *name,
					       int num_flow_table_entries)
{
	return create_ft_common(ns, 0, prio, num_flow_table_entries, name);
}
EXPORT_SYMBOL(mlx5_create_flow_table);

static void _fs_del_ft(struct mlx5_flow_table *ft)
{
	int err;
	struct mlx5_core_dev *dev = fs_get_dev(&ft->base);
	struct fs_prio *prio;

	err = mlx5_cmd_fs_destroy_ft(dev, ft->vport, ft->type, ft->id);
	if (err)
		mlx5_core_warn(dev, "flow steering can't destroy ft %s\n",
			       ft->base.name);

	fs_get_parent(prio, ft);
	prio->num_ft--;
}

static int update_root_ft_destroy(struct mlx5_flow_root_namespace *root,
				    struct mlx5_flow_table *ft)
{
	int err = 0;
	struct fs_prio *prio;
	struct mlx5_flow_table *next_ft = NULL;
	struct mlx5_flow_table *put_ft = NULL;

	if (root->root_ft != ft)
		return 0;

	fs_get_parent(prio, ft);
	/*Assuming objs containis only flow tables and
	 * flow tables are sorted by level.
	 */
	if (!list_is_last(&ft->base.list, &prio->objs)) {
		next_ft = list_next_entry(ft, base.list);
	} else {
		next_ft = find_next_ft(prio);
		put_ft = next_ft;
	}

	if (next_ft) {
		err = mlx5_cmd_update_root_ft(root->dev, next_ft->type,
					      next_ft->id);
		if (err)
			mlx5_core_warn(root->dev, "Update root flow table of id=%u failed\n",
				       ft->id);
	}
	if (!err)
		root->root_ft = next_ft;

	if (put_ft)
		fs_put(&put_ft->base);

	return err;
}

/*Objects in the same prio are destroyed in the reverse order they were createrd*/
int mlx5_destroy_flow_table(struct mlx5_flow_table *ft)
{
	int err = 0;
	struct fs_prio *prio;
	struct mlx5_flow_root_namespace *root;
	bool is_shared_prio;
	struct mlx5_core_dev *dev;

	fs_get_parent(prio, ft);
	root = find_root(&prio->base);
	dev = fs_get_dev(&prio->base);

	if (!root) {
		mlx5_core_err(dev,
		    "flow steering failed to find root of priority %s",
		    prio->base.name);
		return -ENODEV;
	}

	is_shared_prio = prio->flags & MLX5_CORE_FS_PRIO_SHARED;
	if (is_shared_prio) {
		mutex_lock(&prio->shared_lock);
		if (ft->shared_refcount > 1) {
			--ft->shared_refcount;
			fs_put(&ft->base);
			mutex_unlock(&prio->shared_lock);
			return 0;
		}
	}

	mutex_lock(&prio->base.lock);
	mutex_lock(&ft->base.lock);

	err = update_root_ft_destroy(root, ft);
	if (err)
		goto unlock_ft;

	/* delete two last entries */
	destroy_star_rule(ft, prio);

	mutex_unlock(&ft->base.lock);
	fs_remove_node_parent_locked(&ft->base);
	mutex_unlock(&prio->base.lock);
	if (is_shared_prio)
		mutex_unlock(&prio->shared_lock);

	return err;

unlock_ft:
	mutex_unlock(&ft->base.lock);
	mutex_unlock(&prio->base.lock);
	if (is_shared_prio)
		mutex_unlock(&prio->shared_lock);

	return err;
}
EXPORT_SYMBOL(mlx5_destroy_flow_table);

static struct mlx5_flow_group *fs_create_fg(struct mlx5_core_dev *dev,
					    struct mlx5_flow_table *ft,
					    struct list_head *prev,
					    u32 *fg_in,
					    int refcount)
{
	struct mlx5_flow_group *fg;
	int err;
	unsigned int end_index;
	char name[20];

	fg = fs_alloc_fg(fg_in);
	if (IS_ERR(fg))
		return fg;

	end_index = fg->start_index + fg->max_ftes - 1;
	err =  mlx5_cmd_fs_create_fg(dev, fg_in,
				     ft->vport, ft->type, ft->id,
				     &fg->id);
	if (err)
		goto free_fg;

	mutex_lock(&ft->base.lock);
	if (ft->autogroup.active)
		ft->autogroup.num_types++;

	snprintf(name, sizeof(name), "group_%u", fg->id);
	/*Add node to tree*/
	fs_add_node(&fg->base, &ft->base, name, refcount);
	/*Add node to group list*/
	list_add(&fg->base.list, prev);
	mutex_unlock(&ft->base.lock);

	return fg;

free_fg:
	kfree(fg);
	return ERR_PTR(err);
}

struct mlx5_flow_group *mlx5_create_flow_group(struct mlx5_flow_table *ft,
					       u32 *in)
{
	struct mlx5_flow_group *fg;
	struct mlx5_core_dev *dev = fs_get_dev(&ft->base);

	if (!dev)
		return ERR_PTR(-ENODEV);

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

	fg = fs_create_fg(dev, ft, ft->fgs.prev, in, 1);

	return fg;
}
EXPORT_SYMBOL(mlx5_create_flow_group);

/*Group is destoyed when all the rules in the group were removed*/
static void fs_del_fg(struct mlx5_flow_group *fg)
{
	struct mlx5_flow_table *parent_ft;
	struct mlx5_core_dev *dev;

	fs_get_parent(parent_ft, fg);
	dev = fs_get_dev(&parent_ft->base);
	WARN_ON(!dev);

	if (parent_ft->autogroup.active)
		parent_ft->autogroup.num_types--;

	if (mlx5_cmd_fs_destroy_fg(dev, parent_ft->vport,
				   parent_ft->type,
				   parent_ft->id, fg->id))
		mlx5_core_warn(dev, "flow steering can't destroy fg\n");
}

void mlx5_destroy_flow_group(struct mlx5_flow_group *fg)
{
	fs_remove_node(&fg->base);
}
EXPORT_SYMBOL(mlx5_destroy_flow_group);

static bool _fs_match_exact_val(void *mask, void *val1, void *val2, size_t size)
{
	unsigned int i;

	/* TODO: optimize by comparing 64bits when possible */
	for (i = 0; i < size; i++, mask++, val1++, val2++)
		if ((*((u8 *)val1) & (*(u8 *)mask)) !=
		    ((*(u8 *)val2) & (*(u8 *)mask)))
			return false;

	return true;
}

bool fs_match_exact_val(struct mlx5_core_fs_mask *mask,
			       void *val1, void *val2)
{
	if (mask->match_criteria_enable &
	    1 << MLX5_CREATE_FLOW_GROUP_IN_MATCH_CRITERIA_ENABLE_OUTER_HEADERS) {
		void *fte_match1 = MLX5_ADDR_OF(fte_match_param,
						val1, outer_headers);
		void *fte_match2 = MLX5_ADDR_OF(fte_match_param,
						val2, outer_headers);
		void *fte_mask = MLX5_ADDR_OF(fte_match_param,
					      mask->match_criteria, outer_headers);

		if (!_fs_match_exact_val(fte_mask, fte_match1, fte_match2,
					 MLX5_ST_SZ_BYTES(fte_match_set_lyr_2_4)))
			return false;
	}

	if (mask->match_criteria_enable &
	    1 << MLX5_CREATE_FLOW_GROUP_IN_MATCH_CRITERIA_ENABLE_MISC_PARAMETERS) {
		void *fte_match1 = MLX5_ADDR_OF(fte_match_param,
						val1, misc_parameters);
		void *fte_match2 = MLX5_ADDR_OF(fte_match_param,
						val2, misc_parameters);
		void *fte_mask = MLX5_ADDR_OF(fte_match_param,
					  mask->match_criteria, misc_parameters);

		if (!_fs_match_exact_val(fte_mask, fte_match1, fte_match2,
					 MLX5_ST_SZ_BYTES(fte_match_set_misc)))
			return false;
	}
	if (mask->match_criteria_enable &
	    1 << MLX5_CREATE_FLOW_GROUP_IN_MATCH_CRITERIA_ENABLE_INNER_HEADERS) {
		void *fte_match1 = MLX5_ADDR_OF(fte_match_param,
						val1, inner_headers);
		void *fte_match2 = MLX5_ADDR_OF(fte_match_param,
						val2, inner_headers);
		void *fte_mask = MLX5_ADDR_OF(fte_match_param,
					  mask->match_criteria, inner_headers);

		if (!_fs_match_exact_val(fte_mask, fte_match1, fte_match2,
					 MLX5_ST_SZ_BYTES(fte_match_set_lyr_2_4)))
			return false;
	}
	return true;
}

bool fs_match_exact_mask(u8 match_criteria_enable1,
				u8 match_criteria_enable2,
				void *mask1, void *mask2)
{
	return match_criteria_enable1 == match_criteria_enable2 &&
		!memcmp(mask1, mask2, MLX5_ST_SZ_BYTES(fte_match_param));
}

static struct mlx5_flow_table *find_first_ft_in_ns_reverse(struct mlx5_flow_namespace *ns,
							   struct list_head *start);

static struct mlx5_flow_table *_find_first_ft_in_prio_reverse(struct fs_prio *prio,
							      struct list_head *start)
{
	struct fs_base *it = container_of(start, struct fs_base, list);

	if (!prio)
		return NULL;

	fs_for_each_ns_or_ft_continue_reverse(it, prio) {
		struct mlx5_flow_namespace	*ns;
		struct mlx5_flow_table		*ft;

		if (it->type == FS_TYPE_FLOW_TABLE) {
			fs_get_obj(ft, it);
			fs_get(&ft->base);
			return ft;
		}

		fs_get_obj(ns, it);
		WARN_ON(ns->base.type != FS_TYPE_NAMESPACE);

		ft = find_first_ft_in_ns_reverse(ns, &ns->prios);
		if (ft)
			return ft;
	}

	return NULL;
}

static struct mlx5_flow_table *find_first_ft_in_prio_reverse(struct fs_prio *prio,
							     struct list_head *start)
{
	struct mlx5_flow_table *ft;

	if (!prio)
		return NULL;

	mutex_lock(&prio->base.lock);
	ft = _find_first_ft_in_prio_reverse(prio, start);
	mutex_unlock(&prio->base.lock);

	return ft;
}

static struct mlx5_flow_table *find_first_ft_in_ns_reverse(struct mlx5_flow_namespace *ns,
							   struct list_head *start)
{
	struct fs_prio *prio;

	if (!ns)
		return NULL;

	fs_get_obj(prio, container_of(start, struct fs_base, list));
	mutex_lock(&ns->base.lock);
	fs_for_each_prio_continue_reverse(prio, ns) {
		struct mlx5_flow_table *ft;

		ft = find_first_ft_in_prio_reverse(prio, &prio->objs);
		if (ft) {
			mutex_unlock(&ns->base.lock);
			return ft;
		}
	}
	mutex_unlock(&ns->base.lock);

	return NULL;
}

/* Returned a held ft, assumed curr is protected, assumed curr's parent is
 * locked
 */
static struct mlx5_flow_table *find_prev_ft(struct mlx5_flow_table *curr,
					    struct fs_prio *prio)
{
	struct mlx5_flow_table *ft = NULL;
	struct fs_base *curr_base;

	if (!curr)
		return NULL;

	/* prio has either namespace or flow-tables, but not both */
	if (!list_empty(&prio->objs) &&
	    list_first_entry(&prio->objs, struct mlx5_flow_table, base.list) !=
	    curr)
		return NULL;

	while (!ft && prio) {
		struct mlx5_flow_namespace *ns;

		fs_get_parent(ns, prio);
		ft = find_first_ft_in_ns_reverse(ns, &prio->base.list);
		curr_base = &ns->base;
		fs_get_parent(prio, ns);

		if (prio && !ft)
			ft = find_first_ft_in_prio_reverse(prio,
							   &curr_base->list);
	}
	return ft;
}

static struct mlx5_flow_table *_find_first_ft_in_prio(struct fs_prio *prio,
						      struct list_head *start)
{
	struct fs_base	*it = container_of(start, struct fs_base, list);

	if (!prio)
		return NULL;

	fs_for_each_ns_or_ft_continue(it, prio) {
		struct mlx5_flow_namespace	*ns;
		struct mlx5_flow_table		*ft;

		if (it->type == FS_TYPE_FLOW_TABLE) {
			fs_get_obj(ft, it);
			fs_get(&ft->base);
			return ft;
		}

		fs_get_obj(ns, it);
		WARN_ON(ns->base.type != FS_TYPE_NAMESPACE);

		ft = find_first_ft_in_ns(ns, &ns->prios);
		if (ft)
			return ft;
	}

	return NULL;
}

static struct mlx5_flow_table *find_first_ft_in_prio(struct fs_prio *prio,
						     struct list_head *start)
{
	struct mlx5_flow_table *ft;

	if (!prio)
		return NULL;

	mutex_lock(&prio->base.lock);
	ft = _find_first_ft_in_prio(prio, start);
	mutex_unlock(&prio->base.lock);

	return ft;
}

static struct mlx5_flow_table *find_first_ft_in_ns(struct mlx5_flow_namespace *ns,
						   struct list_head *start)
{
	struct fs_prio *prio;

	if (!ns)
		return NULL;

	fs_get_obj(prio, container_of(start, struct fs_base, list));
	mutex_lock(&ns->base.lock);
	fs_for_each_prio_continue(prio, ns) {
		struct mlx5_flow_table *ft;

		ft = find_first_ft_in_prio(prio, &prio->objs);
		if (ft) {
			mutex_unlock(&ns->base.lock);
			return ft;
		}
	}
	mutex_unlock(&ns->base.lock);

	return NULL;
}

/* returned a held ft, assumed curr is protected, assumed curr's parent is
 * locked
 */
static struct mlx5_flow_table *find_next_ft(struct fs_prio *prio)
{
	struct mlx5_flow_table *ft = NULL;
	struct fs_base *curr_base;

	while (!ft && prio) {
		struct mlx5_flow_namespace *ns;

		fs_get_parent(ns, prio);
		ft = find_first_ft_in_ns(ns, &prio->base.list);
		curr_base = &ns->base;
		fs_get_parent(prio, ns);

		if (!ft && prio)
			ft = _find_first_ft_in_prio(prio, &curr_base->list);
	}
	return ft;
}


/* called under ft mutex lock */
static struct mlx5_flow_group *create_autogroup(struct mlx5_flow_table *ft,
						u8 match_criteria_enable,
						u32 *match_criteria)
{
	unsigned int group_size;
	unsigned int candidate_index = 0;
	unsigned int candidate_group_num = 0;
	struct mlx5_flow_group *g;
	struct mlx5_flow_group *ret;
	struct list_head *prev = &ft->fgs;
	struct mlx5_core_dev *dev;
	u32 *in;
	int inlen = MLX5_ST_SZ_BYTES(create_flow_group_in);
	void *match_criteria_addr;

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

	dev = fs_get_dev(&ft->base);
	if (!dev)
		return ERR_PTR(-ENODEV);

	in = mlx5_vzalloc(inlen);
	if (!in) {
		mlx5_core_warn(dev, "failed to allocate inbox\n");
		return ERR_PTR(-ENOMEM);
	}


	if (ft->autogroup.num_types < ft->autogroup.max_types)
		group_size = ft->max_fte / (ft->autogroup.max_types + 1);
	else
		group_size = 1;

	if (group_size == 0) {
		mlx5_core_warn(dev,
			       "flow steering can't create group size of 0\n");
		ret = ERR_PTR(-EINVAL);
		goto out;
	}

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

	if (candidate_index + group_size > ft->max_fte) {
		ret = ERR_PTR(-ENOSPC);
		goto out;
	}

	MLX5_SET(create_flow_group_in, in, match_criteria_enable,
		 match_criteria_enable);
	MLX5_SET(create_flow_group_in, in, start_flow_index, candidate_index);
	MLX5_SET(create_flow_group_in, in, end_flow_index,   candidate_index +
		 group_size - 1);
	match_criteria_addr = MLX5_ADDR_OF(create_flow_group_in,
					   in, match_criteria);
	memcpy(match_criteria_addr, match_criteria,
	       MLX5_ST_SZ_BYTES(fte_match_param));

	ret = fs_create_fg(dev, ft, prev, in, 0);
out:
	kvfree(in);
	return ret;
}

static struct mlx5_flow_namespace *get_ns_with_notifiers(struct fs_base *node)
{
	struct mlx5_flow_namespace *ns = NULL;

	while (node  && (node->type != FS_TYPE_NAMESPACE ||
			      list_empty(&container_of(node, struct
						       mlx5_flow_namespace,
						       base)->list_notifiers)))
		node = node->parent;

	if (node)
		fs_get_obj(ns, node);

	return ns;
}


/*Assumption- fte is locked*/
static void call_to_add_rule_notifiers(struct mlx5_flow_rule *dst,
				      struct fs_fte *fte)
{
	struct mlx5_flow_namespace *ns;
	struct mlx5_flow_handler *iter_handler;
	struct fs_client_priv_data *iter_client;
	void *data;
	bool is_new_rule = list_first_entry(&fte->dests,
					    struct mlx5_flow_rule,
					    base.list) == dst;
	int err;

	ns = get_ns_with_notifiers(&fte->base);
	if (!ns)
		return;

	down_read(&ns->notifiers_rw_sem);
	list_for_each_entry(iter_handler, &ns->list_notifiers,
			    list) {
		if (iter_handler->add_dst_cb) {
			data = NULL;
			mutex_lock(&dst->clients_lock);
			list_for_each_entry(
				iter_client, &dst->clients_data, list) {
				if (iter_client->fs_handler == iter_handler) {
					data = iter_client->client_dst_data;
					break;
				}
			}
			mutex_unlock(&dst->clients_lock);
			err  = iter_handler->add_dst_cb(dst,
							is_new_rule,
							NULL,
							iter_handler->client_context);
			if (err)
				break;
		}
	}
	up_read(&ns->notifiers_rw_sem);
}

static void call_to_del_rule_notifiers(struct mlx5_flow_rule *dst,
				      struct fs_fte *fte)
{
	struct mlx5_flow_namespace *ns;
	struct mlx5_flow_handler *iter_handler;
	struct fs_client_priv_data *iter_client;
	void *data;
	bool ctx_changed = (fte->dests_size == 0);

	ns = get_ns_with_notifiers(&fte->base);
	if (!ns)
		return;
	down_read(&ns->notifiers_rw_sem);
	list_for_each_entry(iter_handler, &ns->list_notifiers,
			    list) {
		data = NULL;
		mutex_lock(&dst->clients_lock);
		list_for_each_entry(iter_client, &dst->clients_data, list) {
			if (iter_client->fs_handler == iter_handler) {
				data = iter_client->client_dst_data;
				break;
			}
		}
		mutex_unlock(&dst->clients_lock);
		if (iter_handler->del_dst_cb) {
			iter_handler->del_dst_cb(dst, ctx_changed, data,
						 iter_handler->client_context);
		}
	}
	up_read(&ns->notifiers_rw_sem);
}

/* fte should not be deleted while calling this function */
static struct mlx5_flow_rule *_fs_add_dst_fte(struct fs_fte *fte,
					      struct mlx5_flow_group *fg,
					      struct mlx5_flow_destination *dest)
{
	struct mlx5_flow_table *ft;
	struct mlx5_flow_rule *dst;
	int err;

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

	memcpy(&dst->dest_attr, dest, sizeof(*dest));
	dst->base.type = FS_TYPE_FLOW_DEST;
	INIT_LIST_HEAD(&dst->clients_data);
	mutex_init(&dst->clients_lock);
	fs_get_parent(ft, fg);
	/*Add dest to dests list- added as first element after the head*/
	list_add_tail(&dst->base.list, &fte->dests);
	fte->dests_size++;
	err = mlx5_cmd_fs_set_fte(fs_get_dev(&ft->base),
				  ft->vport,
				  &fte->status,
				  fte->val, ft->type,
				  ft->id, fte->index, fg->id, fte->flow_tag,
				  fte->action, fte->dests_size, &fte->dests);
	if (err)
		goto free_dst;

	list_del(&dst->base.list);

	return dst;

free_dst:
	list_del(&dst->base.list);
	kfree(dst);
	fte->dests_size--;
	return ERR_PTR(err);
}

static char *get_dest_name(struct mlx5_flow_destination *dest)
{
	char *name = kzalloc(sizeof(char) * 20, GFP_KERNEL);

	switch (dest->type) {
	case MLX5_FLOW_CONTEXT_DEST_TYPE_FLOW_TABLE:
		snprintf(name, 20, "dest_%s_%u", "flow_table",
			 dest->ft->id);
		return name;
	case MLX5_FLOW_CONTEXT_DEST_TYPE_VPORT:
		snprintf(name, 20, "dest_%s_%u", "vport",
			 dest->vport_num);
		return name;
	case MLX5_FLOW_CONTEXT_DEST_TYPE_TIR:
		snprintf(name, 20, "dest_%s_%u", "tir", dest->tir_num);
		return name;
	default:
		kfree(name);
		return NULL;
	}
}

/* assumed fg is locked */
static unsigned int fs_get_free_fg_index(struct mlx5_flow_group *fg,
					 struct list_head **prev)
{
	struct fs_fte *fte;
	unsigned int start = fg->start_index;

	if (prev)
		*prev = &fg->ftes;

	/* assumed list is sorted by index */
	fs_for_each_fte(fte, fg) {
		if (fte->index != start)
			return start;
		start++;
		if (prev)
			*prev = &fte->base.list;
	}

	return start;
}


static struct fs_fte *fs_create_fte(struct mlx5_flow_group *fg,
			     u32 *match_value,
			     u8 action,
			     u32 flow_tag,
			     struct list_head **prev)
{
	struct fs_fte *fte;
	int index = 0;

	index = fs_get_free_fg_index(fg, prev);
	fte = fs_alloc_fte(action, flow_tag, match_value, index);
	if (IS_ERR(fte))
		return fte;

	return fte;
}

static void add_rule_to_tree(struct mlx5_flow_rule *rule,
			     struct fs_fte *fte)
{
	char *dest_name;

	dest_name = get_dest_name(&rule->dest_attr);
	fs_add_node(&rule->base, &fte->base, dest_name, 1);
	/* re-add to list, since fs_add_node reset our list */
	list_add_tail(&rule->base.list, &fte->dests);
	kfree(dest_name);
	call_to_add_rule_notifiers(rule, fte);
}

static void fs_del_dst(struct mlx5_flow_rule *dst)
{
	struct mlx5_flow_table *ft;
	struct mlx5_flow_group *fg;
	struct fs_fte *fte;
	u32	*match_value;
	struct mlx5_core_dev *dev = fs_get_dev(&dst->base);
	int match_len = MLX5_ST_SZ_BYTES(fte_match_param);
	int err;

	WARN_ON(!dev);

	match_value = mlx5_vzalloc(match_len);
	if (!match_value) {
		mlx5_core_warn(dev, "failed to allocate inbox\n");
		return;
	}

	fs_get_parent(fte, dst);
	fs_get_parent(fg, fte);
	mutex_lock(&fg->base.lock);
	memcpy(match_value, fte->val, sizeof(fte->val));
	/* ft can't be changed as fg is locked */
	fs_get_parent(ft, fg);
	list_del(&dst->base.list);
	fte->dests_size--;
	if (fte->dests_size) {
		err = mlx5_cmd_fs_set_fte(dev, ft->vport,
					  &fte->status, match_value, ft->type,
					  ft->id, fte->index, fg->id,
					  fte->flow_tag, fte->action,
					  fte->dests_size, &fte->dests);
		if (err) {
			mlx5_core_warn(dev, "%s can't delete dst %s\n",
				       __func__, dst->base.name);
			goto err;
		}
	}
	call_to_del_rule_notifiers(dst, fte);
err:
	mutex_unlock(&fg->base.lock);
	kvfree(match_value);
}

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

	fs_get_parent(fg, fte);
	fs_get_parent(ft, fg);

	dev = fs_get_dev(&ft->base);
	WARN_ON(!dev);

	err = mlx5_cmd_fs_delete_fte(dev, ft->vport, &fte->status,
				     ft->type, ft->id, fte->index);
	if (err)
		mlx5_core_warn(dev, "flow steering can't delete fte %s\n",
			       fte->base.name);

	fg->num_ftes--;
}

/* assuming parent fg is locked */
/* Add dst algorithm */
static struct mlx5_flow_rule *fs_add_dst_fg(struct mlx5_flow_group *fg,
						   u32 *match_value,
						   u8 action,
						   u32 flow_tag,
						   struct mlx5_flow_destination *dest)
{
	struct fs_fte *fte;
	struct mlx5_flow_rule *dst;
	struct mlx5_flow_table *ft;
	struct list_head *prev;
	char fte_name[20];

	mutex_lock(&fg->base.lock);
	fs_for_each_fte(fte, fg) {
		/* TODO: Check of size against PRM max size */
		mutex_lock(&fte->base.lock);
		if (fs_match_exact_val(&fg->mask, match_value, &fte->val) &&
		    action == fte->action && flow_tag == fte->flow_tag) {
			dst = _fs_add_dst_fte(fte, fg, dest);
			mutex_unlock(&fte->base.lock);
			if (IS_ERR(dst))
				goto unlock_fg;
			goto add_rule;
		}
		mutex_unlock(&fte->base.lock);
	}

	fs_get_parent(ft, fg);
	if (fg->num_ftes == fg->max_ftes) {
		dst = ERR_PTR(-ENOSPC);
		goto unlock_fg;
	}

	fte = fs_create_fte(fg, match_value, action, flow_tag, &prev);
	if (IS_ERR(fte)) {
		dst = (void *)fte;
		goto unlock_fg;
	}
	dst = _fs_add_dst_fte(fte, fg, dest);
	if (IS_ERR(dst)) {
		kfree(fte);
		goto unlock_fg;
	}

	fg->num_ftes++;

	snprintf(fte_name, sizeof(fte_name), "fte%u", fte->index);
	/* Add node to tree */
	fs_add_node(&fte->base, &fg->base, fte_name, 0);
	list_add(&fte->base.list, prev);
add_rule:
	add_rule_to_tree(dst, fte);
unlock_fg:
	mutex_unlock(&fg->base.lock);
	return dst;
}

static struct mlx5_flow_rule *fs_add_dst_ft(struct mlx5_flow_table *ft,
					    u8 match_criteria_enable,
					    u32 *match_criteria,
					    u32 *match_value,
					    u8 action, u32 flow_tag,
					    struct mlx5_flow_destination *dest)
{
	/*? where dst_entry is allocated*/
	struct mlx5_flow_group *g;
	struct mlx5_flow_rule *dst;

	fs_get(&ft->base);
	mutex_lock(&ft->base.lock);
	fs_for_each_fg(g, ft)
		if (fs_match_exact_mask(g->mask.match_criteria_enable,
					match_criteria_enable,
					g->mask.match_criteria,
					match_criteria)) {
			mutex_unlock(&ft->base.lock);

			dst = fs_add_dst_fg(g, match_value,
					    action, flow_tag, dest);
			if (PTR_ERR(dst) && PTR_ERR(dst) != -ENOSPC)
				goto unlock;
		}
	mutex_unlock(&ft->base.lock);

	g = create_autogroup(ft, match_criteria_enable, match_criteria);
	if (IS_ERR(g)) {
		dst = (void *)g;
		goto unlock;
	}

	dst = fs_add_dst_fg(g, match_value,
			    action, flow_tag, dest);
	if (IS_ERR(dst)) {
		/* Remove assumes refcount > 0 and autogroup creates a group
		 * with a refcount = 0.
		 */
		fs_get(&g->base);
		fs_remove_node(&g->base);
		goto unlock;
	}

unlock:
	fs_put(&ft->base);
	return dst;
}

struct mlx5_flow_rule *
mlx5_add_flow_rule(struct mlx5_flow_table *ft,
		   u8 match_criteria_enable,
		   u32 *match_criteria,
		   u32 *match_value,
		   u32 action,
		   u32 flow_tag,
		   struct mlx5_flow_destination *dest)
{
	struct mlx5_flow_rule *dst;
	struct mlx5_flow_namespace *ns;

	ns = get_ns_with_notifiers(&ft->base);
	if (ns)
		down_read(&ns->dests_rw_sem);
	dst =  fs_add_dst_ft(ft, match_criteria_enable, match_criteria,
			     match_value, action, flow_tag, dest);
	if (ns)
		up_read(&ns->dests_rw_sem);

	return dst;


}
EXPORT_SYMBOL(mlx5_add_flow_rule);

void mlx5_del_flow_rule(struct mlx5_flow_rule *dst)
{
	struct mlx5_flow_namespace *ns;

	ns = get_ns_with_notifiers(&dst->base);
	if (ns)
		down_read(&ns->dests_rw_sem);
	fs_remove_node(&dst->base);
	if (ns)
		up_read(&ns->dests_rw_sem);
}
EXPORT_SYMBOL(mlx5_del_flow_rule);

#define MLX5_CORE_FS_ROOT_NS_NAME "root"
#define MLX5_CORE_FS_ESW_EGRESS_ACL "esw_egress_root"
#define MLX5_CORE_FS_ESW_INGRESS_ACL "esw_ingress_root"
#define MLX5_CORE_FS_FDB_ROOT_NS_NAME "fdb_root"
#define MLX5_CORE_FS_SNIFFER_RX_ROOT_NS_NAME "sniffer_rx_root"
#define MLX5_CORE_FS_SNIFFER_TX_ROOT_NS_NAME "sniffer_tx_root"
#define MLX5_CORE_FS_PRIO_MAX_FT 4
#define MLX5_CORE_FS_PRIO_MAX_NS 1

static struct fs_prio *fs_create_prio(struct mlx5_flow_namespace *ns,
				      unsigned prio, int max_ft,
				      const char *name, u8 flags)
{
	struct fs_prio *fs_prio;

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

	fs_prio->base.type = FS_TYPE_PRIO;
	fs_add_node(&fs_prio->base, &ns->base, name, 1);
	fs_prio->max_ft = max_ft;
	fs_prio->max_ns = MLX5_CORE_FS_PRIO_MAX_NS;
	fs_prio->prio = prio;
	fs_prio->flags = flags;
	list_add_tail(&fs_prio->base.list, &ns->prios);
	INIT_LIST_HEAD(&fs_prio->objs);
	mutex_init(&fs_prio->shared_lock);

	return fs_prio;
}

static void cleanup_root_ns(struct mlx5_core_dev *dev)
{
	struct mlx5_flow_root_namespace *root_ns = dev->root_ns;
	struct fs_prio *iter_prio;

	if (!root_ns)
		return;

	/* stage 1 */
	fs_for_each_prio(iter_prio, &root_ns->ns) {
		struct mlx5_flow_namespace *iter_ns;

		fs_for_each_ns(iter_ns, iter_prio) {
			while (!list_empty(&iter_ns->prios)) {
				struct fs_base *iter_prio2 =
					list_first_entry(&iter_ns->prios,
							 struct fs_base,
							 list);

				fs_remove_node(iter_prio2);
			}
		}
	}

	/* stage 2 */
	fs_for_each_prio(iter_prio, &root_ns->ns) {
		while (!list_empty(&iter_prio->objs)) {
			struct fs_base *iter_ns =
				list_first_entry(&iter_prio->objs,
						 struct fs_base,
						 list);

				fs_remove_node(iter_ns);
		}
	}
	/* stage 3 */
	while (!list_empty(&root_ns->ns.prios)) {
		struct fs_base *iter_prio =
			list_first_entry(&root_ns->ns.prios,
					 struct fs_base,
					 list);

		fs_remove_node(iter_prio);
	}

	fs_remove_node(&root_ns->ns.base);
	dev->root_ns = NULL;
}

static void cleanup_single_prio_root_ns(struct mlx5_core_dev *dev,
					struct mlx5_flow_root_namespace *root_ns)
{
	struct fs_base *prio;

	if (!root_ns)
		return;

	if (!list_empty(&root_ns->ns.prios)) {
		prio = list_first_entry(&root_ns->ns.prios,
					struct fs_base,
				 list);
		fs_remove_node(prio);
	}
	fs_remove_node(&root_ns->ns.base);
	root_ns = NULL;
}

void mlx5_cleanup_fs(struct mlx5_core_dev *dev)
{
	cleanup_root_ns(dev);
	cleanup_single_prio_root_ns(dev, dev->sniffer_rx_root_ns);
	cleanup_single_prio_root_ns(dev, dev->sniffer_tx_root_ns);
	cleanup_single_prio_root_ns(dev, dev->fdb_root_ns);
	cleanup_single_prio_root_ns(dev, dev->esw_egress_root_ns);
	cleanup_single_prio_root_ns(dev, dev->esw_ingress_root_ns);
}

static struct mlx5_flow_namespace *fs_init_namespace(struct mlx5_flow_namespace
						 *ns)
{
	ns->base.type = FS_TYPE_NAMESPACE;
	init_rwsem(&ns->dests_rw_sem);
	init_rwsem(&ns->notifiers_rw_sem);
	INIT_LIST_HEAD(&ns->prios);
	INIT_LIST_HEAD(&ns->list_notifiers);

	return ns;
}

static struct mlx5_flow_root_namespace *create_root_ns(struct mlx5_core_dev *dev,
							  enum fs_ft_type
							  table_type,
							  char *name)
{
	struct mlx5_flow_root_namespace *root_ns;
	struct mlx5_flow_namespace *ns;

	/* create the root namespace */
	root_ns = mlx5_vzalloc(sizeof(*root_ns));
	if (!root_ns)
		goto err;

	root_ns->dev = dev;
	root_ns->table_type = table_type;
	mutex_init(&root_ns->fs_chain_lock);

	ns = &root_ns->ns;
	fs_init_namespace(ns);
	fs_add_node(&ns->base, NULL, name, 1);

	return root_ns;
err:
	return NULL;
}

static int init_fdb_root_ns(struct mlx5_core_dev *dev)
{
	struct fs_prio *prio;

	dev->fdb_root_ns = create_root_ns(dev, FS_FT_FDB,
					  MLX5_CORE_FS_FDB_ROOT_NS_NAME);
	if (!dev->fdb_root_ns)
		return -ENOMEM;

	/* create 1 prio*/
	prio = fs_create_prio(&dev->fdb_root_ns->ns, 0, 1, "fdb_prio", 0);
	if (IS_ERR(prio))
		return PTR_ERR(prio);
	else
		return 0;
}

#define MAX_VPORTS 128

static int init_egress_acl_root_ns(struct mlx5_core_dev *dev)
{
	struct fs_prio *prio;

	dev->esw_egress_root_ns = create_root_ns(dev, FS_FT_ESW_EGRESS_ACL,
						 MLX5_CORE_FS_ESW_EGRESS_ACL);
	if (!dev->esw_egress_root_ns)
		return -ENOMEM;

	/* create 1 prio*/
	prio = fs_create_prio(&dev->esw_egress_root_ns->ns, 0, MAX_VPORTS,
			      "esw_egress_prio", 0);
	if (IS_ERR(prio))
		return PTR_ERR(prio);
	else
		return 0;
}

static int init_ingress_acl_root_ns(struct mlx5_core_dev *dev)
{
	struct fs_prio *prio;

	dev->esw_ingress_root_ns = create_root_ns(dev, FS_FT_ESW_INGRESS_ACL,
						  MLX5_CORE_FS_ESW_INGRESS_ACL);
	if (!dev->esw_ingress_root_ns)
		return -ENOMEM;

	/* create 1 prio*/
	prio = fs_create_prio(&dev->esw_ingress_root_ns->ns, 0, MAX_VPORTS,
			      "esw_ingress_prio", 0);
	if (IS_ERR(prio))
		return PTR_ERR(prio);
	else
		return 0;
}

static int init_sniffer_rx_root_ns(struct mlx5_core_dev *dev)
{
	struct fs_prio *prio;

	dev->sniffer_rx_root_ns = create_root_ns(dev, FS_FT_SNIFFER_RX,
				     MLX5_CORE_FS_SNIFFER_RX_ROOT_NS_NAME);
	if (!dev->sniffer_rx_root_ns)
		return  -ENOMEM;

	/* create 1 prio*/
	prio = fs_create_prio(&dev->sniffer_rx_root_ns->ns, 0, 1,
			      "sniffer_prio", 0);
	if (IS_ERR(prio))
		return PTR_ERR(prio);
	else
		return 0;
}


static int init_sniffer_tx_root_ns(struct mlx5_core_dev *dev)
{
	struct fs_prio *prio;

	dev->sniffer_tx_root_ns = create_root_ns(dev, FS_FT_SNIFFER_TX,
						 MLX5_CORE_FS_SNIFFER_TX_ROOT_NS_NAME);
	if (!dev->sniffer_tx_root_ns)
		return  -ENOMEM;

	/* create 1 prio*/
	prio = fs_create_prio(&dev->sniffer_tx_root_ns->ns, 0, 1,
			      "sniffer_prio", 0);
	if (IS_ERR(prio))
		return PTR_ERR(prio);
	else
		return 0;
}

static struct mlx5_flow_namespace *fs_create_namespace(struct fs_prio *prio,
						       const char *name)
{
	struct mlx5_flow_namespace	*ns;

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

	fs_init_namespace(ns);
	fs_add_node(&ns->base, &prio->base, name, 1);
	list_add_tail(&ns->base.list, &prio->objs);

	return ns;
}

#define FLOW_TABLE_BIT_SZ 1
#define GET_FLOW_TABLE_CAP(dev, offset) \
	((be32_to_cpu(*((__be32 *)(dev->hca_caps_cur[MLX5_CAP_FLOW_TABLE]) +	\
			offset / 32)) >>					\
	  (32 - FLOW_TABLE_BIT_SZ - (offset & 0x1f))) & FLOW_TABLE_BIT_SZ)

static bool has_required_caps(struct mlx5_core_dev *dev, struct node_caps *caps)
{
	int i;

	for (i = 0; i < caps->arr_sz; i++) {
		if (!GET_FLOW_TABLE_CAP(dev, caps->caps[i]))
			return false;
	}
	return true;
}

static int _init_root_tree(struct mlx5_core_dev *dev, int max_ft_level,
		    struct init_tree_node *node, struct fs_base *base_parent,
		    struct init_tree_node *tree_parent)
{
	struct mlx5_flow_namespace *fs_ns;
	struct fs_prio *fs_prio;
	int priority;
	struct fs_base *base;
	int i;
	int err = 0;

	if (node->type == FS_TYPE_PRIO) {
		if ((node->min_ft_level > max_ft_level) ||
		    !has_required_caps(dev, &node->caps))
			goto out;

		fs_get_obj(fs_ns, base_parent);
		priority = node - tree_parent->children;
		fs_prio = fs_create_prio(fs_ns, priority,
					 node->max_ft,
					 node->name, node->flags);
		if (IS_ERR(fs_prio)) {
			err = PTR_ERR(fs_prio);
			goto out;
		}
		base = &fs_prio->base;
	} else if (node->type == FS_TYPE_NAMESPACE) {
		fs_get_obj(fs_prio, base_parent);
		fs_ns = fs_create_namespace(fs_prio, node->name);
		if (IS_ERR(fs_ns)) {
			err = PTR_ERR(fs_ns);
			goto out;
		}
		base = &fs_ns->base;
	} else {
		return -EINVAL;
	}
	for (i = 0; i < node->ar_size; i++) {
		err = _init_root_tree(dev, max_ft_level, &node->children[i], base,
				      node);
		if (err)
			break;
	}
out:
	return err;
}

static int init_root_tree(struct mlx5_core_dev *dev, int max_ft_level,
		   struct init_tree_node *node, struct fs_base *parent)
{
	int i;
	struct mlx5_flow_namespace *fs_ns;
	int err = 0;

	fs_get_obj(fs_ns, parent);
	for (i = 0; i < node->ar_size; i++) {
		err = _init_root_tree(dev, max_ft_level,
				      &node->children[i], &fs_ns->base, node);
		if (err)
			break;
	}
	return err;
}

static int sum_max_ft_in_prio(struct fs_prio *prio);
static int sum_max_ft_in_ns(struct mlx5_flow_namespace *ns)
{
	struct fs_prio *prio;
	int sum = 0;

	fs_for_each_prio(prio, ns) {
		sum += sum_max_ft_in_prio(prio);
	}
	return  sum;
}

static int sum_max_ft_in_prio(struct fs_prio *prio)
{
	int sum = 0;
	struct fs_base *it;
	struct mlx5_flow_namespace	*ns;

	if (prio->max_ft)
		return prio->max_ft;

	fs_for_each_ns_or_ft(it, prio) {
		if (it->type == FS_TYPE_FLOW_TABLE)
			continue;

		fs_get_obj(ns, it);
		sum += sum_max_ft_in_ns(ns);
	}
	prio->max_ft = sum;
	return  sum;
}

static void set_max_ft(struct mlx5_flow_namespace *ns)
{
	struct fs_prio *prio;

	if (!ns)
		return;

	fs_for_each_prio(prio, ns)
		sum_max_ft_in_prio(prio);
}

static int init_root_ns(struct mlx5_core_dev *dev)
{
	int max_ft_level = MLX5_CAP_FLOWTABLE(dev,
					      flow_table_properties_nic_receive.
					      max_ft_level);

	dev->root_ns = create_root_ns(dev, FS_FT_NIC_RX,
				      MLX5_CORE_FS_ROOT_NS_NAME);
	if (IS_ERR_OR_NULL(dev->root_ns))
		goto err;


	if (init_root_tree(dev, max_ft_level, &root_fs, &dev->root_ns->ns.base))
		goto err;

	set_max_ft(&dev->root_ns->ns);

	return 0;
err:
	return -ENOMEM;
}

u8 mlx5_get_match_criteria_enable(struct mlx5_flow_rule *rule)
{
	struct fs_base *pbase;
	struct mlx5_flow_group *fg;

	pbase = rule->base.parent;
	WARN_ON(!pbase);
	pbase = pbase->parent;
	WARN_ON(!pbase);

	fs_get_obj(fg, pbase);
	return fg->mask.match_criteria_enable;
}

void mlx5_get_match_value(u32 *match_value,
			  struct mlx5_flow_rule *rule)
{
	struct fs_base *pbase;
	struct fs_fte *fte;

	pbase = rule->base.parent;
	WARN_ON(!pbase);
	fs_get_obj(fte, pbase);

	memcpy(match_value, fte->val, sizeof(fte->val));
}

void mlx5_get_match_criteria(u32 *match_criteria,
			     struct mlx5_flow_rule *rule)
{
	struct fs_base *pbase;
	struct mlx5_flow_group *fg;

	pbase = rule->base.parent;
	WARN_ON(!pbase);
	pbase = pbase->parent;
	WARN_ON(!pbase);

	fs_get_obj(fg, pbase);
	memcpy(match_criteria, &fg->mask.match_criteria,
	       sizeof(fg->mask.match_criteria));
}

int mlx5_init_fs(struct mlx5_core_dev *dev)
{
	int err;

	if (MLX5_CAP_GEN(dev, nic_flow_table)) {
		err = init_root_ns(dev);
		if (err)
			goto err;
	}

	err = init_fdb_root_ns(dev);
	if (err)
		goto err;

	err = init_egress_acl_root_ns(dev);
	if (err)
		goto err;

	err = init_ingress_acl_root_ns(dev);
	if (err)
		goto err;

	err = init_sniffer_tx_root_ns(dev);
	if (err)
		goto err;

	err = init_sniffer_rx_root_ns(dev);
	if (err)
		goto err;

	return 0;
err:
	mlx5_cleanup_fs(dev);
	return err;
}

struct mlx5_flow_namespace *mlx5_get_flow_namespace(struct mlx5_core_dev *dev,
						  enum mlx5_flow_namespace_type type)
{
	struct mlx5_flow_root_namespace *root_ns = dev->root_ns;
	int prio;
	static struct fs_prio *fs_prio;
	struct mlx5_flow_namespace *ns;

	switch (type) {
	case MLX5_FLOW_NAMESPACE_BYPASS:
		prio = 0;
		break;
	case MLX5_FLOW_NAMESPACE_KERNEL:
		prio = 1;
		break;
	case MLX5_FLOW_NAMESPACE_LEFTOVERS:
		prio = 2;
		break;
	case MLX5_FLOW_NAMESPACE_FDB:
		if (dev->fdb_root_ns)
			return &dev->fdb_root_ns->ns;
		else
			return NULL;
	case MLX5_FLOW_NAMESPACE_ESW_EGRESS:
		if (dev->esw_egress_root_ns)
			return &dev->esw_egress_root_ns->ns;
		else
			return NULL;
	case MLX5_FLOW_NAMESPACE_ESW_INGRESS:
		if (dev->esw_ingress_root_ns)
			return &dev->esw_ingress_root_ns->ns;
		else
			return NULL;
	case MLX5_FLOW_NAMESPACE_SNIFFER_RX:
		if (dev->sniffer_rx_root_ns)
			return &dev->sniffer_rx_root_ns->ns;
		else
			return NULL;
	case MLX5_FLOW_NAMESPACE_SNIFFER_TX:
		if (dev->sniffer_tx_root_ns)
			return &dev->sniffer_tx_root_ns->ns;
		else
			return NULL;
	default:
		return NULL;
	}

	if (!root_ns)
		return NULL;

	fs_prio = find_prio(&root_ns->ns, prio);
	if (!fs_prio)
		return NULL;

	ns = list_first_entry(&fs_prio->objs,
			      typeof(*ns),
			      base.list);

	return ns;
}
EXPORT_SYMBOL(mlx5_get_flow_namespace);


int mlx5_set_rule_private_data(struct mlx5_flow_rule *rule,
				  struct mlx5_flow_handler *fs_handler,
				  void  *client_data)
{
	struct fs_client_priv_data *priv_data;

	mutex_lock(&rule->clients_lock);
	/*Check that hanlder isn't exists in the list already*/
	list_for_each_entry(priv_data, &rule->clients_data, list) {
		if (priv_data->fs_handler == fs_handler) {
			priv_data->client_dst_data = client_data;
			goto unlock;
		}
	}
	priv_data = kzalloc(sizeof(*priv_data), GFP_KERNEL);
	if (!priv_data) {
		mutex_unlock(&rule->clients_lock);
		return -ENOMEM;
	}

	priv_data->client_dst_data = client_data;
	priv_data->fs_handler = fs_handler;
	list_add(&priv_data->list, &rule->clients_data);

unlock:
	mutex_unlock(&rule->clients_lock);

	return 0;
}

static int remove_from_clients(struct mlx5_flow_rule *rule,
			bool ctx_changed,
			void *client_data,
			void *context)
{
	struct fs_client_priv_data *iter_client;
	struct fs_client_priv_data *temp_client;
	struct mlx5_flow_handler *handler = (struct
						mlx5_flow_handler*)context;

	mutex_lock(&rule->clients_lock);
	list_for_each_entry_safe(iter_client, temp_client,
				 &rule->clients_data, list) {
		if (iter_client->fs_handler == handler) {
			list_del(&iter_client->list);
			kfree(iter_client);
			break;
		}
	}
	mutex_unlock(&rule->clients_lock);

	return 0;
}

struct mlx5_flow_handler *mlx5_register_rule_notifier(struct mlx5_core_dev *dev,
								enum mlx5_flow_namespace_type ns_type,
								rule_event_fn add_cb,
								rule_event_fn del_cb,
								void *context)
{
	struct mlx5_flow_namespace *ns;
	struct mlx5_flow_handler *handler;

	ns = mlx5_get_flow_namespace(dev, ns_type);
	if (!ns)
		return ERR_PTR(-EINVAL);

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

	handler->add_dst_cb = add_cb;
	handler->del_dst_cb = del_cb;
	handler->client_context = context;
	handler->ns = ns;
	down_write(&ns->notifiers_rw_sem);
	list_add_tail(&handler->list, &ns->list_notifiers);
	up_write(&ns->notifiers_rw_sem);

	return handler;
}

static void iterate_rules_in_ns(struct mlx5_flow_namespace *ns,
				rule_event_fn add_rule_cb,
				void *context);

void mlx5_unregister_rule_notifier(struct mlx5_flow_handler *handler)
{
	struct mlx5_flow_namespace *ns = handler->ns;

	/*Remove from dst's clients*/
	down_write(&ns->dests_rw_sem);
	down_write(&ns->notifiers_rw_sem);
	iterate_rules_in_ns(ns, remove_from_clients, handler);
	list_del(&handler->list);
	up_write(&ns->notifiers_rw_sem);
	up_write(&ns->dests_rw_sem);
	kfree(handler);
}

static void iterate_rules_in_ft(struct mlx5_flow_table *ft,
				rule_event_fn add_rule_cb,
				void *context)
{
	struct mlx5_flow_group *iter_fg;
	struct fs_fte *iter_fte;
	struct mlx5_flow_rule *iter_rule;
	int err = 0;
	bool is_new_rule;

	mutex_lock(&ft->base.lock);
	fs_for_each_fg(iter_fg, ft) {
		mutex_lock(&iter_fg->base.lock);
		fs_for_each_fte(iter_fte, iter_fg) {
			mutex_lock(&iter_fte->base.lock);
			is_new_rule = true;
			fs_for_each_dst(iter_rule, iter_fte) {
				fs_get(&iter_rule->base);
				err = add_rule_cb(iter_rule,
						 is_new_rule,
						 NULL,
						 context);
				fs_put_parent_locked(&iter_rule->base);
				if (err)
					break;
				is_new_rule = false;
			}
			mutex_unlock(&iter_fte->base.lock);
			if (err)
				break;
		}
		mutex_unlock(&iter_fg->base.lock);
		if (err)
			break;
	}
	mutex_unlock(&ft->base.lock);
}

static void iterate_rules_in_prio(struct fs_prio *prio,
				  rule_event_fn add_rule_cb,
				  void *context)
{
	struct fs_base *it;

	mutex_lock(&prio->base.lock);
	fs_for_each_ns_or_ft(it, prio) {
		if (it->type == FS_TYPE_FLOW_TABLE) {
			struct mlx5_flow_table	      *ft;

			fs_get_obj(ft, it);
			iterate_rules_in_ft(ft, add_rule_cb, context);
		} else {
			struct mlx5_flow_namespace *ns;

			fs_get_obj(ns, it);
			iterate_rules_in_ns(ns, add_rule_cb, context);
		}
	}
	mutex_unlock(&prio->base.lock);
}

static void iterate_rules_in_ns(struct mlx5_flow_namespace *ns,
				rule_event_fn add_rule_cb,
				void *context)
{
	struct fs_prio *iter_prio;

	mutex_lock(&ns->base.lock);
	fs_for_each_prio(iter_prio, ns) {
		iterate_rules_in_prio(iter_prio, add_rule_cb, context);
	}
	mutex_unlock(&ns->base.lock);
}

void mlx5_flow_iterate_existing_rules(struct mlx5_flow_namespace *ns,
					 rule_event_fn add_rule_cb,
					 void *context)
{
	down_write(&ns->dests_rw_sem);
	down_read(&ns->notifiers_rw_sem);
	iterate_rules_in_ns(ns, add_rule_cb, context);
	up_read(&ns->notifiers_rw_sem);
	up_write(&ns->dests_rw_sem);
}


void mlx5_del_flow_rules_list(struct mlx5_flow_rules_list *rules_list)
{
	struct mlx5_flow_rule_node *iter_node;
	struct mlx5_flow_rule_node *temp_node;

	list_for_each_entry_safe(iter_node, temp_node, &rules_list->head, list) {
		list_del(&iter_node->list);
		kfree(iter_node);
	}

	kfree(rules_list);
}

#define ROCEV1_ETHERTYPE 0x8915
static int set_rocev1_rules(struct list_head *rules_list)
{
	struct mlx5_flow_rule_node *rocev1_rule;

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

	rocev1_rule->match_criteria_enable =
		1 << MLX5_CREATE_FLOW_GROUP_IN_MATCH_CRITERIA_ENABLE_OUTER_HEADERS;
	MLX5_SET(fte_match_set_lyr_2_4, rocev1_rule->match_criteria, ethertype,
		 0xffff);
	MLX5_SET(fte_match_set_lyr_2_4, rocev1_rule->match_value, ethertype,
		 ROCEV1_ETHERTYPE);

	list_add_tail(&rocev1_rule->list, rules_list);

	return 0;
}

#define ROCEV2_UDP_PORT 4791
static int set_rocev2_rules(struct list_head *rules_list)
{
	struct mlx5_flow_rule_node *ipv4_rule;
	struct mlx5_flow_rule_node *ipv6_rule;

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

	ipv6_rule = kzalloc(sizeof(*ipv6_rule), GFP_KERNEL);
	if (!ipv6_rule) {
		kfree(ipv4_rule);
		return -ENOMEM;
	}

	ipv4_rule->match_criteria_enable =
		1 << MLX5_CREATE_FLOW_GROUP_IN_MATCH_CRITERIA_ENABLE_OUTER_HEADERS;
	MLX5_SET(fte_match_set_lyr_2_4, ipv4_rule->match_criteria, ethertype,
		 0xffff);
	MLX5_SET(fte_match_set_lyr_2_4, ipv4_rule->match_value, ethertype,
		 0x0800);
	MLX5_SET(fte_match_set_lyr_2_4, ipv4_rule->match_criteria, ip_protocol,
		 0xff);
	MLX5_SET(fte_match_set_lyr_2_4, ipv4_rule->match_value, ip_protocol,
		 IPPROTO_UDP);
	MLX5_SET(fte_match_set_lyr_2_4, ipv4_rule->match_criteria, udp_dport,
		 0xffff);
	MLX5_SET(fte_match_set_lyr_2_4, ipv4_rule->match_value, udp_dport,
		 ROCEV2_UDP_PORT);

	ipv6_rule->match_criteria_enable =
		1 << MLX5_CREATE_FLOW_GROUP_IN_MATCH_CRITERIA_ENABLE_OUTER_HEADERS;
	MLX5_SET(fte_match_set_lyr_2_4, ipv6_rule->match_criteria, ethertype,
		 0xffff);
	MLX5_SET(fte_match_set_lyr_2_4, ipv6_rule->match_value, ethertype,
		 0x86dd);
	MLX5_SET(fte_match_set_lyr_2_4, ipv6_rule->match_criteria, ip_protocol,
		 0xff);
	MLX5_SET(fte_match_set_lyr_2_4, ipv6_rule->match_value, ip_protocol,
		 IPPROTO_UDP);
	MLX5_SET(fte_match_set_lyr_2_4, ipv6_rule->match_criteria, udp_dport,
		 0xffff);
	MLX5_SET(fte_match_set_lyr_2_4, ipv6_rule->match_value, udp_dport,
		 ROCEV2_UDP_PORT);

	list_add_tail(&ipv4_rule->list, rules_list);
	list_add_tail(&ipv6_rule->list, rules_list);

	return 0;
}


struct mlx5_flow_rules_list *get_roce_flow_rules(u8 roce_mode)
{
	int err = 0;
	struct mlx5_flow_rules_list *rules_list =
		kzalloc(sizeof(*rules_list), GFP_KERNEL);

	if (!rules_list)
		return NULL;

	INIT_LIST_HEAD(&rules_list->head);

	if (roce_mode & MLX5_ROCE_VERSION_1_CAP) {
		err = set_rocev1_rules(&rules_list->head);
		if (err)
			goto free_list;
	}
	if (roce_mode & MLX5_ROCE_VERSION_2_CAP)
		err = set_rocev2_rules(&rules_list->head);
	if (err)
		goto free_list;

	return rules_list;

free_list:
	mlx5_del_flow_rules_list(rules_list);
	return NULL;
}
