net/mlx5e: Split offloaded eswitch TC rules for port mirroring
[sfrench/cifs-2.6.git] / drivers / net / ethernet / mellanox / mlx5 / core / en_tc.c
index e84bcea8b071399db28474f0f7dd8fbd14c2de70..9372d914abe5cdafdbdf79acb80940eb73ca83c9 100644 (file)
 #include "eswitch.h"
 #include "vxlan.h"
 #include "fs_core.h"
+#include "en/port.h"
 
 struct mlx5_nic_flow_attr {
        u32 action;
        u32 flow_tag;
        u32 mod_hdr_id;
        u32 hairpin_tirn;
+       u8 match_level;
        struct mlx5_flow_table  *hairpin_ft;
 };
 
+#define MLX5E_TC_FLOW_BASE (MLX5E_TC_LAST_EXPORTED_BIT + 1)
+
 enum {
-       MLX5E_TC_FLOW_ESWITCH   = BIT(0),
-       MLX5E_TC_FLOW_NIC       = BIT(1),
-       MLX5E_TC_FLOW_OFFLOADED = BIT(2),
-       MLX5E_TC_FLOW_HAIRPIN   = BIT(3),
-       MLX5E_TC_FLOW_HAIRPIN_RSS = BIT(4),
+       MLX5E_TC_FLOW_INGRESS   = MLX5E_TC_INGRESS,
+       MLX5E_TC_FLOW_EGRESS    = MLX5E_TC_EGRESS,
+       MLX5E_TC_FLOW_ESWITCH   = BIT(MLX5E_TC_FLOW_BASE),
+       MLX5E_TC_FLOW_NIC       = BIT(MLX5E_TC_FLOW_BASE + 1),
+       MLX5E_TC_FLOW_OFFLOADED = BIT(MLX5E_TC_FLOW_BASE + 2),
+       MLX5E_TC_FLOW_HAIRPIN   = BIT(MLX5E_TC_FLOW_BASE + 3),
+       MLX5E_TC_FLOW_HAIRPIN_RSS = BIT(MLX5E_TC_FLOW_BASE + 4),
 };
 
+#define MLX5E_TC_MAX_SPLITS 1
+
 struct mlx5e_tc_flow {
        struct rhash_head       node;
+       struct mlx5e_priv       *priv;
        u64                     cookie;
        u8                      flags;
-       struct mlx5_flow_handle *rule;
+       struct mlx5_flow_handle *rule[MLX5E_TC_MAX_SPLITS + 1];
        struct list_head        encap;   /* flows sharing the same encap ID */
        struct list_head        mod_hdr; /* flows sharing the same mod hdr ID */
        struct list_head        hairpin; /* flows sharing the same hairpin */
@@ -607,7 +616,7 @@ static int mlx5e_hairpin_flow_add(struct mlx5e_priv *priv,
 
        params.q_counter = priv->q_counter;
        /* set hairpin pair per each 50Gbs share of the link */
-       mlx5e_get_max_linkspeed(priv->mdev, &link_speed);
+       mlx5e_port_max_linkspeed(priv->mdev, &link_speed);
        link_speed = max_t(u32, link_speed, 50000);
        link_speed64 = link_speed;
        do_div(link_speed64, 50000);
@@ -753,7 +762,9 @@ mlx5e_tc_add_nic_flow(struct mlx5e_priv *priv,
                table_created = true;
        }
 
-       parse_attr->spec.match_criteria_enable = MLX5_MATCH_OUTER_HEADERS;
+       if (attr->match_level != MLX5_MATCH_NONE)
+               parse_attr->spec.match_criteria_enable = MLX5_MATCH_OUTER_HEADERS;
+
        rule = mlx5_add_flow_rules(priv->fs.tc.t, &parse_attr->spec,
                                   &flow_act, dest, dest_ix);
 
@@ -785,8 +796,8 @@ static void mlx5e_tc_del_nic_flow(struct mlx5e_priv *priv,
        struct mlx5_nic_flow_attr *attr = flow->nic_attr;
        struct mlx5_fc *counter = NULL;
 
-       counter = mlx5_flow_rule_counter(flow->rule);
-       mlx5_del_flow_rules(flow->rule);
+       counter = mlx5_flow_rule_counter(flow->rule[0]);
+       mlx5_del_flow_rules(flow->rule[0]);
        mlx5_fc_destroy(priv->mdev, counter);
 
        if (!mlx5e_tc_num_filters(priv) && priv->fs.tc.t) {
@@ -835,7 +846,8 @@ mlx5e_tc_add_fdb_flow(struct mlx5e_priv *priv,
                }
                out_priv = netdev_priv(encap_dev);
                rpriv = out_priv->ppriv;
-               attr->out_rep = rpriv->rep;
+               attr->out_rep[attr->out_count] = rpriv->rep;
+               attr->out_mdev[attr->out_count++] = out_priv->mdev;
        }
 
        err = mlx5_eswitch_add_vlan_action(esw, attr);
@@ -860,9 +872,18 @@ mlx5e_tc_add_fdb_flow(struct mlx5e_priv *priv,
                rule = mlx5_eswitch_add_offloaded_rule(esw, &parse_attr->spec, attr);
                if (IS_ERR(rule))
                        goto err_add_rule;
+
+               if (attr->mirror_count) {
+                       flow->rule[1] = mlx5_eswitch_add_fwd_rule(esw, &parse_attr->spec, attr);
+                       if (IS_ERR(flow->rule[1]))
+                               goto err_fwd_rule;
+               }
        }
        return rule;
 
+err_fwd_rule:
+       mlx5_eswitch_del_offloaded_rule(esw, rule, attr);
+       rule = flow->rule[1];
 err_add_rule:
        if (attr->action & MLX5_FLOW_CONTEXT_ACTION_MOD_HDR)
                mlx5e_detach_mod_hdr(priv, flow);
@@ -883,7 +904,9 @@ static void mlx5e_tc_del_fdb_flow(struct mlx5e_priv *priv,
 
        if (flow->flags & MLX5E_TC_FLOW_OFFLOADED) {
                flow->flags &= ~MLX5E_TC_FLOW_OFFLOADED;
-               mlx5_eswitch_del_offloaded_rule(esw, flow->rule, attr);
+               if (attr->mirror_count)
+                       mlx5_eswitch_del_offloaded_rule(esw, flow->rule[1], attr);
+               mlx5_eswitch_del_offloaded_rule(esw, flow->rule[0], attr);
        }
 
        mlx5_eswitch_del_vlan_action(esw, attr);
@@ -919,13 +942,25 @@ void mlx5e_tc_encap_flows_add(struct mlx5e_priv *priv,
        list_for_each_entry(flow, &e->flows, encap) {
                esw_attr = flow->esw_attr;
                esw_attr->encap_id = e->encap_id;
-               flow->rule = mlx5_eswitch_add_offloaded_rule(esw, &esw_attr->parse_attr->spec, esw_attr);
-               if (IS_ERR(flow->rule)) {
-                       err = PTR_ERR(flow->rule);
+               flow->rule[0] = mlx5_eswitch_add_offloaded_rule(esw, &esw_attr->parse_attr->spec, esw_attr);
+               if (IS_ERR(flow->rule[0])) {
+                       err = PTR_ERR(flow->rule[0]);
                        mlx5_core_warn(priv->mdev, "Failed to update cached encapsulation flow, %d\n",
                                       err);
                        continue;
                }
+
+               if (esw_attr->mirror_count) {
+                       flow->rule[1] = mlx5_eswitch_add_fwd_rule(esw, &esw_attr->parse_attr->spec, esw_attr);
+                       if (IS_ERR(flow->rule[1])) {
+                               mlx5_eswitch_del_offloaded_rule(esw, flow->rule[0], esw_attr);
+                               err = PTR_ERR(flow->rule[1]);
+                               mlx5_core_warn(priv->mdev, "Failed to update cached mirror flow, %d\n",
+                                              err);
+                               continue;
+                       }
+               }
+
                flow->flags |= MLX5E_TC_FLOW_OFFLOADED;
        }
 }
@@ -938,8 +973,12 @@ void mlx5e_tc_encap_flows_del(struct mlx5e_priv *priv,
 
        list_for_each_entry(flow, &e->flows, encap) {
                if (flow->flags & MLX5E_TC_FLOW_OFFLOADED) {
+                       struct mlx5_esw_flow_attr *attr = flow->esw_attr;
+
                        flow->flags &= ~MLX5E_TC_FLOW_OFFLOADED;
-                       mlx5_eswitch_del_offloaded_rule(esw, flow->rule, flow->esw_attr);
+                       if (attr->mirror_count)
+                               mlx5_eswitch_del_offloaded_rule(esw, flow->rule[1], attr);
+                       mlx5_eswitch_del_offloaded_rule(esw, flow->rule[0], attr);
                }
        }
 
@@ -974,7 +1013,7 @@ void mlx5e_tc_update_neigh_used_value(struct mlx5e_neigh_hash_entry *nhe)
                        continue;
                list_for_each_entry(flow, &e->flows, encap) {
                        if (flow->flags & MLX5E_TC_FLOW_OFFLOADED) {
-                               counter = mlx5_flow_rule_counter(flow->rule);
+                               counter = mlx5_flow_rule_counter(flow->rule[0]);
                                mlx5_fc_query_cached(counter, &bytes, &packets, &lastuse);
                                if (time_after((unsigned long)lastuse, nhe->reported_lastuse)) {
                                        neigh_used = true;
@@ -1547,6 +1586,11 @@ static int parse_cls_flower(struct mlx5e_priv *priv,
                }
        }
 
+       if (flow->flags & MLX5E_TC_FLOW_ESWITCH)
+               flow->esw_attr->match_level = match_level;
+       else
+               flow->nic_attr->match_level = match_level;
+
        return err;
 }
 
@@ -2068,6 +2112,20 @@ static int mlx5e_route_lookup_ipv4(struct mlx5e_priv *priv,
        return 0;
 }
 
+static bool is_merged_eswitch_dev(struct mlx5e_priv *priv,
+                                 struct net_device *peer_netdev)
+{
+       struct mlx5e_priv *peer_priv;
+
+       peer_priv = netdev_priv(peer_netdev);
+
+       return (MLX5_CAP_ESW(priv->mdev, merged_eswitch) &&
+               (priv->netdev->netdev_ops == peer_netdev->netdev_ops) &&
+               same_hw_devs(priv, peer_priv) &&
+               MLX5_VPORT_MANAGER(peer_priv->mdev) &&
+               (peer_priv->mdev->priv.eswitch->mode == SRIOV_OFFLOADS));
+}
+
 static int mlx5e_route_lookup_ipv6(struct mlx5e_priv *priv,
                                   struct net_device *mirred_dev,
                                   struct net_device **out_dev,
@@ -2489,6 +2547,7 @@ static int parse_tc_fdb_actions(struct mlx5e_priv *priv, struct tcf_exts *exts,
                return -EINVAL;
 
        attr->in_rep = rpriv->rep;
+       attr->in_mdev = priv->mdev;
 
        tcf_exts_to_list(exts, &actions);
        list_for_each_entry(a, &actions, list) {
@@ -2507,6 +2566,7 @@ static int parse_tc_fdb_actions(struct mlx5e_priv *priv, struct tcf_exts *exts,
                                return err;
 
                        action |= MLX5_FLOW_CONTEXT_ACTION_MOD_HDR;
+                       attr->mirror_count = attr->out_count;
                        continue;
                }
 
@@ -2518,19 +2578,27 @@ static int parse_tc_fdb_actions(struct mlx5e_priv *priv, struct tcf_exts *exts,
                        return -EOPNOTSUPP;
                }
 
-               if (is_tcf_mirred_egress_redirect(a)) {
-                       struct net_device *out_dev;
+               if (is_tcf_mirred_egress_redirect(a) || is_tcf_mirred_egress_mirror(a)) {
                        struct mlx5e_priv *out_priv;
+                       struct net_device *out_dev;
 
                        out_dev = tcf_mirred_dev(a);
 
+                       if (attr->out_count >= MLX5_MAX_FLOW_FWD_VPORTS) {
+                               pr_err("can't support more than %d output ports, can't offload forwarding\n",
+                                      attr->out_count);
+                               return -EOPNOTSUPP;
+                       }
+
                        if (switchdev_port_same_parent_id(priv->netdev,
-                                                         out_dev)) {
+                                                         out_dev) ||
+                           is_merged_eswitch_dev(priv, out_dev)) {
                                action |= MLX5_FLOW_CONTEXT_ACTION_FWD_DEST |
                                          MLX5_FLOW_CONTEXT_ACTION_COUNT;
                                out_priv = netdev_priv(out_dev);
                                rpriv = out_priv->ppriv;
-                               attr->out_rep = rpriv->rep;
+                               attr->out_rep[attr->out_count] = rpriv->rep;
+                               attr->out_mdev[attr->out_count++] = out_priv->mdev;
                        } else if (encap) {
                                parse_attr->mirred_ifindex = out_dev->ifindex;
                                parse_attr->tun_info = *info;
@@ -2553,6 +2621,7 @@ static int parse_tc_fdb_actions(struct mlx5e_priv *priv, struct tcf_exts *exts,
                                encap = true;
                        else
                                return -EOPNOTSUPP;
+                       attr->mirror_count = attr->out_count;
                        continue;
                }
 
@@ -2574,6 +2643,7 @@ static int parse_tc_fdb_actions(struct mlx5e_priv *priv, struct tcf_exts *exts,
                        } else { /* action is TCA_VLAN_ACT_MODIFY */
                                return -EOPNOTSUPP;
                        }
+                       attr->mirror_count = attr->out_count;
                        continue;
                }
 
@@ -2589,24 +2659,68 @@ static int parse_tc_fdb_actions(struct mlx5e_priv *priv, struct tcf_exts *exts,
        if (!actions_match_supported(priv, exts, parse_attr, flow))
                return -EOPNOTSUPP;
 
+       if (attr->out_count > 1 && !mlx5_esw_has_fwd_fdb(priv->mdev)) {
+               netdev_warn_once(priv->netdev, "current firmware doesn't support split rule for port mirroring\n");
+               return -EOPNOTSUPP;
+       }
+
        return 0;
 }
 
+static void get_flags(int flags, u8 *flow_flags)
+{
+       u8 __flow_flags = 0;
+
+       if (flags & MLX5E_TC_INGRESS)
+               __flow_flags |= MLX5E_TC_FLOW_INGRESS;
+       if (flags & MLX5E_TC_EGRESS)
+               __flow_flags |= MLX5E_TC_FLOW_EGRESS;
+
+       *flow_flags = __flow_flags;
+}
+
+static const struct rhashtable_params tc_ht_params = {
+       .head_offset = offsetof(struct mlx5e_tc_flow, node),
+       .key_offset = offsetof(struct mlx5e_tc_flow, cookie),
+       .key_len = sizeof(((struct mlx5e_tc_flow *)0)->cookie),
+       .automatic_shrinking = true,
+};
+
+static struct rhashtable *get_tc_ht(struct mlx5e_priv *priv)
+{
+       struct mlx5_eswitch *esw = priv->mdev->priv.eswitch;
+       struct mlx5e_rep_priv *uplink_rpriv;
+
+       if (MLX5_VPORT_MANAGER(priv->mdev) && esw->mode == SRIOV_OFFLOADS) {
+               uplink_rpriv = mlx5_eswitch_get_uplink_priv(esw, REP_ETH);
+               return &uplink_rpriv->tc_ht;
+       } else
+               return &priv->fs.tc.ht;
+}
+
 int mlx5e_configure_flower(struct mlx5e_priv *priv,
-                          struct tc_cls_flower_offload *f)
+                          struct tc_cls_flower_offload *f, int flags)
 {
        struct mlx5_eswitch *esw = priv->mdev->priv.eswitch;
        struct mlx5e_tc_flow_parse_attr *parse_attr;
-       struct mlx5e_tc_table *tc = &priv->fs.tc;
+       struct rhashtable *tc_ht = get_tc_ht(priv);
        struct mlx5e_tc_flow *flow;
        int attr_size, err = 0;
        u8 flow_flags = 0;
 
+       get_flags(flags, &flow_flags);
+
+       flow = rhashtable_lookup_fast(tc_ht, &f->cookie, tc_ht_params);
+       if (flow) {
+               netdev_warn_once(priv->netdev, "flow cookie %lx already exists, ignoring\n", f->cookie);
+               return 0;
+       }
+
        if (esw && esw->mode == SRIOV_OFFLOADS) {
-               flow_flags = MLX5E_TC_FLOW_ESWITCH;
+               flow_flags |= MLX5E_TC_FLOW_ESWITCH;
                attr_size  = sizeof(struct mlx5_esw_flow_attr);
        } else {
-               flow_flags = MLX5E_TC_FLOW_NIC;
+               flow_flags |= MLX5E_TC_FLOW_NIC;
                attr_size  = sizeof(struct mlx5_nic_flow_attr);
        }
 
@@ -2619,6 +2733,7 @@ int mlx5e_configure_flower(struct mlx5e_priv *priv,
 
        flow->cookie = f->cookie;
        flow->flags = flow_flags;
+       flow->priv = priv;
 
        err = parse_cls_flower(priv, flow, &parse_attr->spec, f);
        if (err < 0)
@@ -2628,16 +2743,16 @@ int mlx5e_configure_flower(struct mlx5e_priv *priv,
                err = parse_tc_fdb_actions(priv, f->exts, parse_attr, flow);
                if (err < 0)
                        goto err_free;
-               flow->rule = mlx5e_tc_add_fdb_flow(priv, parse_attr, flow);
+               flow->rule[0] = mlx5e_tc_add_fdb_flow(priv, parse_attr, flow);
        } else {
                err = parse_tc_nic_actions(priv, f->exts, parse_attr, flow);
                if (err < 0)
                        goto err_free;
-               flow->rule = mlx5e_tc_add_nic_flow(priv, parse_attr, flow);
+               flow->rule[0] = mlx5e_tc_add_nic_flow(priv, parse_attr, flow);
        }
 
-       if (IS_ERR(flow->rule)) {
-               err = PTR_ERR(flow->rule);
+       if (IS_ERR(flow->rule[0])) {
+               err = PTR_ERR(flow->rule[0]);
                if (err != -EAGAIN)
                        goto err_free;
        }
@@ -2649,8 +2764,7 @@ int mlx5e_configure_flower(struct mlx5e_priv *priv,
            !(flow->esw_attr->action & MLX5_FLOW_CONTEXT_ACTION_ENCAP))
                kvfree(parse_attr);
 
-       err = rhashtable_insert_fast(&tc->ht, &flow->node,
-                                    tc->ht_params);
+       err = rhashtable_insert_fast(tc_ht, &flow->node, tc_ht_params);
        if (err) {
                mlx5e_tc_del_flow(priv, flow);
                kfree(flow);
@@ -2664,18 +2778,28 @@ err_free:
        return err;
 }
 
+#define DIRECTION_MASK (MLX5E_TC_INGRESS | MLX5E_TC_EGRESS)
+#define FLOW_DIRECTION_MASK (MLX5E_TC_FLOW_INGRESS | MLX5E_TC_FLOW_EGRESS)
+
+static bool same_flow_direction(struct mlx5e_tc_flow *flow, int flags)
+{
+       if ((flow->flags & FLOW_DIRECTION_MASK) == (flags & DIRECTION_MASK))
+               return true;
+
+       return false;
+}
+
 int mlx5e_delete_flower(struct mlx5e_priv *priv,
-                       struct tc_cls_flower_offload *f)
+                       struct tc_cls_flower_offload *f, int flags)
 {
+       struct rhashtable *tc_ht = get_tc_ht(priv);
        struct mlx5e_tc_flow *flow;
-       struct mlx5e_tc_table *tc = &priv->fs.tc;
 
-       flow = rhashtable_lookup_fast(&tc->ht, &f->cookie,
-                                     tc->ht_params);
-       if (!flow)
+       flow = rhashtable_lookup_fast(tc_ht, &f->cookie, tc_ht_params);
+       if (!flow || !same_flow_direction(flow, flags))
                return -EINVAL;
 
-       rhashtable_remove_fast(&tc->ht, &flow->node, tc->ht_params);
+       rhashtable_remove_fast(tc_ht, &flow->node, tc_ht_params);
 
        mlx5e_tc_del_flow(priv, flow);
 
@@ -2685,24 +2809,23 @@ int mlx5e_delete_flower(struct mlx5e_priv *priv,
 }
 
 int mlx5e_stats_flower(struct mlx5e_priv *priv,
-                      struct tc_cls_flower_offload *f)
+                      struct tc_cls_flower_offload *f, int flags)
 {
-       struct mlx5e_tc_table *tc = &priv->fs.tc;
+       struct rhashtable *tc_ht = get_tc_ht(priv);
        struct mlx5e_tc_flow *flow;
        struct mlx5_fc *counter;
        u64 bytes;
        u64 packets;
        u64 lastuse;
 
-       flow = rhashtable_lookup_fast(&tc->ht, &f->cookie,
-                                     tc->ht_params);
-       if (!flow)
+       flow = rhashtable_lookup_fast(tc_ht, &f->cookie, tc_ht_params);
+       if (!flow || !same_flow_direction(flow, flags))
                return -EINVAL;
 
        if (!(flow->flags & MLX5E_TC_FLOW_OFFLOADED))
                return 0;
 
-       counter = mlx5_flow_rule_counter(flow->rule);
+       counter = mlx5_flow_rule_counter(flow->rule[0]);
        if (!counter)
                return 0;
 
@@ -2713,41 +2836,43 @@ int mlx5e_stats_flower(struct mlx5e_priv *priv,
        return 0;
 }
 
-static const struct rhashtable_params mlx5e_tc_flow_ht_params = {
-       .head_offset = offsetof(struct mlx5e_tc_flow, node),
-       .key_offset = offsetof(struct mlx5e_tc_flow, cookie),
-       .key_len = sizeof(((struct mlx5e_tc_flow *)0)->cookie),
-       .automatic_shrinking = true,
-};
-
-int mlx5e_tc_init(struct mlx5e_priv *priv)
+int mlx5e_tc_nic_init(struct mlx5e_priv *priv)
 {
        struct mlx5e_tc_table *tc = &priv->fs.tc;
 
        hash_init(tc->mod_hdr_tbl);
        hash_init(tc->hairpin_tbl);
 
-       tc->ht_params = mlx5e_tc_flow_ht_params;
-       return rhashtable_init(&tc->ht, &tc->ht_params);
+       return rhashtable_init(&tc->ht, &tc_ht_params);
 }
 
 static void _mlx5e_tc_del_flow(void *ptr, void *arg)
 {
        struct mlx5e_tc_flow *flow = ptr;
-       struct mlx5e_priv *priv = arg;
+       struct mlx5e_priv *priv = flow->priv;
 
        mlx5e_tc_del_flow(priv, flow);
        kfree(flow);
 }
 
-void mlx5e_tc_cleanup(struct mlx5e_priv *priv)
+void mlx5e_tc_nic_cleanup(struct mlx5e_priv *priv)
 {
        struct mlx5e_tc_table *tc = &priv->fs.tc;
 
-       rhashtable_free_and_destroy(&tc->ht, _mlx5e_tc_del_flow, priv);
+       rhashtable_free_and_destroy(&tc->ht, _mlx5e_tc_del_flow, NULL);
 
        if (!IS_ERR_OR_NULL(tc->t)) {
                mlx5_destroy_flow_table(tc->t);
                tc->t = NULL;
        }
 }
+
+int mlx5e_tc_esw_init(struct rhashtable *tc_ht)
+{
+       return rhashtable_init(tc_ht, &tc_ht_params);
+}
+
+void mlx5e_tc_esw_cleanup(struct rhashtable *tc_ht)
+{
+       rhashtable_free_and_destroy(tc_ht, _mlx5e_tc_del_flow, NULL);
+}