net/mlx5_core: Introduce forward to next priority action
[cascardo/linux.git] / drivers / net / ethernet / mellanox / mlx5 / core / fs_core.c
index a2781ee..bf34467 100644 (file)
@@ -201,8 +201,10 @@ static void tree_put_node(struct fs_node *node)
 
 static int tree_remove_node(struct fs_node *node)
 {
-       if (atomic_read(&node->refcount) > 1)
-               return -EPERM;
+       if (atomic_read(&node->refcount) > 1) {
+               atomic_dec(&node->refcount);
+               return -EEXIST;
+       }
        tree_put_node(node);
        return 0;
 }
@@ -365,6 +367,11 @@ static void del_rule(struct fs_node *node)
        memcpy(match_value, fte->val, sizeof(fte->val));
        fs_get_obj(ft, fg->node.parent);
        list_del(&rule->node.list);
+       if (rule->sw_action == MLX5_FLOW_CONTEXT_ACTION_FWD_NEXT_PRIO) {
+               mutex_lock(&rule->dest_attr.ft->lock);
+               list_del(&rule->next_ft);
+               mutex_unlock(&rule->dest_attr.ft->lock);
+       }
        fte->dests_size--;
        if (fte->dests_size) {
                err = mlx5_cmd_update_fte(dev, ft,
@@ -470,6 +477,8 @@ static struct mlx5_flow_table *alloc_flow_table(int level, int max_fte,
        ft->node.type = FS_TYPE_FLOW_TABLE;
        ft->type = table_type;
        ft->max_fte = max_fte;
+       INIT_LIST_HEAD(&ft->fwd_rules);
+       mutex_init(&ft->lock);
 
        return ft;
 }
@@ -606,9 +615,63 @@ static int update_root_ft_create(struct mlx5_flow_table *ft, struct fs_prio
        return err;
 }
 
+static int mlx5_modify_rule_destination(struct mlx5_flow_rule *rule,
+                                       struct mlx5_flow_destination *dest)
+{
+       struct mlx5_flow_table *ft;
+       struct mlx5_flow_group *fg;
+       struct fs_fte *fte;
+       int err = 0;
+
+       fs_get_obj(fte, rule->node.parent);
+       if (!(fte->action & MLX5_FLOW_CONTEXT_ACTION_FWD_DEST))
+               return -EINVAL;
+       lock_ref_node(&fte->node);
+       fs_get_obj(fg, fte->node.parent);
+       fs_get_obj(ft, fg->node.parent);
+
+       memcpy(&rule->dest_attr, dest, sizeof(*dest));
+       err = mlx5_cmd_update_fte(get_dev(&ft->node),
+                                 ft, fg->id, fte);
+       unlock_ref_node(&fte->node);
+
+       return err;
+}
+
+/* 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) {
+               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;
        int err = 0;
 
        /* Connect_prev_fts and update_root_ft_create are mutually exclusive */
@@ -617,6 +680,11 @@ static int connect_flow_table(struct mlx5_core_dev *dev, struct mlx5_flow_table
                err = connect_prev_fts(dev, ft, prio);
                if (err)
                        return err;
+
+               next_ft = find_next_chained_ft(prio);
+               err = connect_fwd_rules(dev, ft, next_ft);
+               if (err)
+                       return err;
        }
 
        if (MLX5_CAP_FLOWTABLE(dev,
@@ -767,6 +835,7 @@ static struct mlx5_flow_rule *alloc_rule(struct mlx5_flow_destination *dest)
        if (!rule)
                return NULL;
 
+       INIT_LIST_HEAD(&rule->next_ft);
        rule->node.type = FS_TYPE_FLOW_DEST;
        memcpy(&rule->dest_attr, dest, sizeof(*dest));
 
@@ -787,9 +856,14 @@ static struct mlx5_flow_rule *add_rule_fte(struct fs_fte *fte,
                return ERR_PTR(-ENOMEM);
 
        fs_get_obj(ft, fg->node.parent);
-       /* Add dest to dests list- added as first element after the head */
+       /* 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, 1, del_rule);
-       list_add_tail(&rule->node.list, &fte->node.children);
+       if (dest && dest->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);
        fte->dests_size++;
        if (fte->dests_size == 1)
                err = mlx5_cmd_create_fte(get_dev(&ft->node),
@@ -908,6 +982,25 @@ out:
        return fg;
 }
 
+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 (rule->dest_attr.type == dest->type) {
+                       if ((dest->type == MLX5_FLOW_DESTINATION_TYPE_VPORT &&
+                            dest->vport_num == rule->dest_attr.vport_num) ||
+                           (dest->type == MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE &&
+                            dest->ft == rule->dest_attr.ft) ||
+                           (dest->type == MLX5_FLOW_DESTINATION_TYPE_TIR &&
+                            dest->tir_num == rule->dest_attr.tir_num))
+                               return rule;
+               }
+       }
+       return NULL;
+}
+
 static struct mlx5_flow_rule *add_rule_fg(struct mlx5_flow_group *fg,
                                          u32 *match_value,
                                          u8 action,
@@ -924,6 +1017,13 @@ static struct mlx5_flow_rule *add_rule_fg(struct mlx5_flow_group *fg,
                nested_lock_ref_node(&fte->node, FS_MUTEX_CHILD);
                if (compare_match_value(&fg->mask, match_value, &fte->val) &&
                    action == fte->action && flow_tag == fte->flow_tag) {
+                       rule = find_flow_rule(fte, dest);
+                       if (rule) {
+                               atomic_inc(&rule->node.refcount);
+                               unlock_ref_node(&fte->node);
+                               unlock_ref_node(&fg->node);
+                               return rule;
+                       }
                        rule = add_rule_fte(fte, fg, dest);
                        unlock_ref_node(&fte->node);
                        if (IS_ERR(rule))
@@ -989,14 +1089,14 @@ static struct mlx5_flow_rule *add_rule_to_auto_fg(struct mlx5_flow_table *ft,
        return rule;
 }
 
-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)
+static 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_group *g;
        struct mlx5_flow_rule *rule;
@@ -1019,6 +1119,63 @@ unlock:
        unlock_ref_node(&ft->node);
        return rule;
 }
+
+static bool fwd_next_prio_supported(struct mlx5_flow_table *ft)
+{
+       return ((ft->type == FS_FT_NIC_RX) &&
+               (MLX5_CAP_FLOWTABLE(get_dev(&ft->node), nic_rx_multi_path_tirs)));
+}
+
+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_root_namespace *root = find_root(&ft->node);
+       struct mlx5_flow_destination gen_dest;
+       struct mlx5_flow_table *next_ft = NULL;
+       struct mlx5_flow_rule *rule = NULL;
+       u32 sw_action = action;
+       struct fs_prio *prio;
+
+       fs_get_obj(prio, ft->node.parent);
+       if (action == MLX5_FLOW_CONTEXT_ACTION_FWD_NEXT_PRIO) {
+               if (!fwd_next_prio_supported(ft))
+                       return ERR_PTR(-EOPNOTSUPP);
+               if (dest)
+                       return ERR_PTR(-EINVAL);
+               mutex_lock(&root->chain_lock);
+               next_ft = find_next_chained_ft(prio);
+               if (next_ft) {
+                       gen_dest.type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE;
+                       gen_dest.ft = next_ft;
+                       dest = &gen_dest;
+                       action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST;
+               } else {
+                       mutex_unlock(&root->chain_lock);
+                       return ERR_PTR(-EOPNOTSUPP);
+               }
+       }
+
+       rule =  _mlx5_add_flow_rule(ft, match_criteria_enable, match_criteria,
+                                   match_value, action, flow_tag, dest);
+
+       if (sw_action == MLX5_FLOW_CONTEXT_ACTION_FWD_NEXT_PRIO) {
+               if (!IS_ERR_OR_NULL(rule) &&
+                   (list_empty(&rule->next_ft))) {
+                       mutex_lock(&next_ft->lock);
+                       list_add(&rule->next_ft, &next_ft->fwd_rules);
+                       mutex_unlock(&next_ft->lock);
+                       rule->sw_action = MLX5_FLOW_CONTEXT_ACTION_FWD_NEXT_PRIO;
+               }
+               mutex_unlock(&root->chain_lock);
+       }
+       return rule;
+}
 EXPORT_SYMBOL(mlx5_add_flow_rule);
 
 void mlx5_del_flow_rule(struct mlx5_flow_rule *rule)
@@ -1082,6 +1239,10 @@ static int disconnect_flow_table(struct mlx5_flow_table *ft)
                return 0;
 
        next_ft = find_next_chained_ft(prio);
+       err = connect_fwd_rules(dev, next_ft, ft);
+       if (err)
+               return err;
+
        err = connect_prev_fts(dev, next_ft, prio);
        if (err)
                mlx5_core_warn(dev, "Failed to disconnect flow table %d\n",