root/drivers/net/ethernet/mellanox/mlx5/core/esw/acl/egress_lgcy.c
// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
/* Copyright (c) 2020 Mellanox Technologies Inc. All rights reserved. */

#include "mlx5_core.h"
#include "eswitch.h"
#include "helper.h"
#include "lgcy.h"

static void esw_acl_egress_lgcy_rules_destroy(struct mlx5_vport *vport)
{
        esw_acl_egress_vlan_destroy(vport);
        if (!IS_ERR_OR_NULL(vport->egress.legacy.drop_rule)) {
                mlx5_del_flow_rules(vport->egress.legacy.drop_rule);
                vport->egress.legacy.drop_rule = NULL;
        }
}

static int esw_acl_egress_lgcy_groups_create(struct mlx5_eswitch *esw,
                                             struct mlx5_vport *vport)
{
        int inlen = MLX5_ST_SZ_BYTES(create_flow_group_in);
        struct mlx5_core_dev *dev = esw->dev;
        struct mlx5_flow_group *drop_grp;
        u32 *flow_group_in;
        int err = 0;

        err = esw_acl_egress_vlan_grp_create(esw, vport);
        if (err)
                return err;

        flow_group_in = kvzalloc(inlen, GFP_KERNEL);
        if (!flow_group_in) {
                err = -ENOMEM;
                goto alloc_err;
        }

        MLX5_SET(create_flow_group_in, flow_group_in, start_flow_index, 1);
        MLX5_SET(create_flow_group_in, flow_group_in, end_flow_index, 1);
        drop_grp = mlx5_create_flow_group(vport->egress.acl, flow_group_in);
        if (IS_ERR(drop_grp)) {
                err = PTR_ERR(drop_grp);
                esw_warn(dev, "Failed to create E-Switch vport[%d] egress drop flow group, err(%d)\n",
                         vport->vport, err);
                goto drop_grp_err;
        }

        vport->egress.legacy.drop_grp = drop_grp;
        kvfree(flow_group_in);
        return 0;

drop_grp_err:
        kvfree(flow_group_in);
alloc_err:
        esw_acl_egress_vlan_grp_destroy(vport);
        return err;
}

static void esw_acl_egress_lgcy_groups_destroy(struct mlx5_vport *vport)
{
        if (!IS_ERR_OR_NULL(vport->egress.legacy.drop_grp)) {
                mlx5_destroy_flow_group(vport->egress.legacy.drop_grp);
                vport->egress.legacy.drop_grp = NULL;
        }
        esw_acl_egress_vlan_grp_destroy(vport);
}

int esw_acl_egress_lgcy_setup(struct mlx5_eswitch *esw,
                              struct mlx5_vport *vport)
{
        bool vst_mode_steering = esw_vst_mode_is_steering(esw);
        struct mlx5_flow_destination drop_ctr_dst = {};
        struct mlx5_flow_destination *dst = NULL;
        struct mlx5_fc *drop_counter = NULL;
        struct mlx5_flow_act flow_act = {};
        /* The egress acl table contains 2 rules:
         * 1)Allow traffic with vlan_tag=vst_vlan_id
         * 2)Drop all other traffic.
         */
        int table_size = 2;
        int dest_num = 0;
        int actions_flag;
        int err = 0;

        if (vport->egress.legacy.drop_counter) {
                drop_counter = vport->egress.legacy.drop_counter;
        } else if (MLX5_CAP_ESW_EGRESS_ACL(esw->dev, flow_counter)) {
                drop_counter = mlx5_fc_create(esw->dev, false);
                if (IS_ERR(drop_counter)) {
                        esw_warn(esw->dev,
                                 "vport[%d] configure egress drop rule counter err(%pe)\n",
                                 vport->vport, drop_counter);
                        drop_counter = NULL;
                }
                vport->egress.legacy.drop_counter = drop_counter;
        }

        esw_acl_egress_lgcy_rules_destroy(vport);

        if (!vport->info.vlan && !vport->info.qos) {
                esw_acl_egress_lgcy_cleanup(esw, vport);
                return 0;
        }

        if (!vport->egress.acl) {
                vport->egress.acl = esw_acl_table_create(esw, vport,
                                                         MLX5_FLOW_NAMESPACE_ESW_EGRESS,
                                                         table_size);
                if (IS_ERR(vport->egress.acl)) {
                        err = PTR_ERR(vport->egress.acl);
                        vport->egress.acl = NULL;
                        goto out;
                }

                err = esw_acl_egress_lgcy_groups_create(esw, vport);
                if (err)
                        goto out;
        }

        esw_debug(esw->dev,
                  "vport[%d] configure egress rules, vlan(%d) qos(%d)\n",
                  vport->vport, vport->info.vlan, vport->info.qos);

        /* Allowed vlan rule */
        actions_flag = MLX5_FLOW_CONTEXT_ACTION_ALLOW;
        if (vst_mode_steering)
                actions_flag |= MLX5_FLOW_CONTEXT_ACTION_VLAN_POP;
        err = esw_egress_acl_vlan_create(esw, vport, NULL, vport->info.vlan,
                                         actions_flag);
        if (err)
                goto out;

        flow_act.action = MLX5_FLOW_CONTEXT_ACTION_DROP;

        /* Attach egress drop flow counter */
        if (drop_counter) {
                flow_act.action |= MLX5_FLOW_CONTEXT_ACTION_COUNT;
                drop_ctr_dst.type = MLX5_FLOW_DESTINATION_TYPE_COUNTER;
                drop_ctr_dst.counter = drop_counter;
                dst = &drop_ctr_dst;
                dest_num++;
        }
        vport->egress.legacy.drop_rule =
                mlx5_add_flow_rules(vport->egress.acl, NULL,
                                    &flow_act, dst, dest_num);
        if (IS_ERR(vport->egress.legacy.drop_rule)) {
                err = PTR_ERR(vport->egress.legacy.drop_rule);
                esw_warn(esw->dev,
                         "vport[%d] configure egress drop rule failed, err(%d)\n",
                         vport->vport, err);
                vport->egress.legacy.drop_rule = NULL;
                goto out;
        }

        return err;

out:
        esw_acl_egress_lgcy_cleanup(esw, vport);
        return err;
}

void esw_acl_egress_lgcy_cleanup(struct mlx5_eswitch *esw,
                                 struct mlx5_vport *vport)
{
        if (IS_ERR_OR_NULL(vport->egress.acl))
                goto clean_drop_counter;

        esw_debug(esw->dev, "Destroy vport[%d] E-Switch egress ACL\n", vport->vport);

        esw_acl_egress_lgcy_rules_destroy(vport);
        esw_acl_egress_lgcy_groups_destroy(vport);
        esw_acl_egress_table_destroy(vport);

clean_drop_counter:
        if (vport->egress.legacy.drop_counter) {
                mlx5_fc_destroy(esw->dev, vport->egress.legacy.drop_counter);
                vport->egress.legacy.drop_counter = NULL;
        }
}