root/drivers/net/ethernet/mellanox/mlx5/core/esw/acl/egress_ofld.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 "ofld.h"

static void esw_acl_egress_ofld_fwd2vport_destroy(struct mlx5_vport *vport)
{
        if (!vport->egress.offloads.fwd_rule)
                return;

        mlx5_del_flow_rules(vport->egress.offloads.fwd_rule);
        vport->egress.offloads.fwd_rule = NULL;
}

void esw_acl_egress_ofld_bounce_rule_destroy(struct mlx5_vport *vport, int rule_index)
{
        struct mlx5_flow_handle *bounce_rule =
                xa_load(&vport->egress.offloads.bounce_rules, rule_index);

        if (!bounce_rule)
                return;

        mlx5_del_flow_rules(bounce_rule);
        xa_erase(&vport->egress.offloads.bounce_rules, rule_index);
}

static void esw_acl_egress_ofld_bounce_rules_destroy(struct mlx5_vport *vport)
{
        struct mlx5_flow_handle *bounce_rule;
        unsigned long i;

        xa_for_each(&vport->egress.offloads.bounce_rules, i, bounce_rule) {
                mlx5_del_flow_rules(bounce_rule);
                xa_erase(&vport->egress.offloads.bounce_rules, i);
        }
}

static int esw_acl_egress_ofld_fwd2vport_create(struct mlx5_eswitch *esw,
                                                struct mlx5_vport *vport,
                                                struct mlx5_flow_destination *fwd_dest)
{
        struct mlx5_flow_act flow_act = {};
        int err = 0;

        esw_debug(esw->dev, "vport(%d) configure egress acl rule fwd2vport(%d)\n",
                  vport->vport, fwd_dest->vport.num);

        /* Delete the old egress forward-to-vport rule if any */
        esw_acl_egress_ofld_fwd2vport_destroy(vport);

        flow_act.action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST;

        vport->egress.offloads.fwd_rule =
                mlx5_add_flow_rules(vport->egress.acl, NULL,
                                    &flow_act, fwd_dest, 1);
        if (IS_ERR(vport->egress.offloads.fwd_rule)) {
                err = PTR_ERR(vport->egress.offloads.fwd_rule);
                esw_warn(esw->dev,
                         "vport(%d) failed to add fwd2vport acl rule err(%d)\n",
                         vport->vport, err);
                vport->egress.offloads.fwd_rule = NULL;
        }

        return err;
}

static int esw_acl_egress_ofld_rules_create(struct mlx5_eswitch *esw,
                                            struct mlx5_vport *vport,
                                            struct mlx5_flow_destination *fwd_dest)
{
        int err = 0;
        int action;

        if (MLX5_CAP_GEN(esw->dev, prio_tag_required)) {
                /* For prio tag mode, there is only 1 FTEs:
                 * 1) prio tag packets - pop the prio tag VLAN, allow
                 * Unmatched traffic is allowed by default
                 */
                esw_debug(esw->dev,
                          "vport[%d] configure prio tag egress rules\n", vport->vport);

                action = MLX5_FLOW_CONTEXT_ACTION_VLAN_POP;
                action |= fwd_dest ? MLX5_FLOW_CONTEXT_ACTION_FWD_DEST :
                          MLX5_FLOW_CONTEXT_ACTION_ALLOW;

                /* prio tag vlan rule - pop it so vport receives untagged packets */
                err = esw_egress_acl_vlan_create(esw, vport, fwd_dest, 0, action);
                if (err)
                        goto prio_err;
        }

        if (fwd_dest) {
                err = esw_acl_egress_ofld_fwd2vport_create(esw, vport, fwd_dest);
                if (err)
                        goto fwd_err;
        }

        return 0;

fwd_err:
        esw_acl_egress_vlan_destroy(vport);
prio_err:
        return err;
}

static void esw_acl_egress_ofld_rules_destroy(struct mlx5_vport *vport)
{
        esw_acl_egress_vlan_destroy(vport);
        esw_acl_egress_ofld_fwd2vport_destroy(vport);
        esw_acl_egress_ofld_bounce_rules_destroy(vport);
}

static int esw_acl_egress_ofld_groups_create(struct mlx5_eswitch *esw,
                                             struct mlx5_vport *vport)
{
        int inlen = MLX5_ST_SZ_BYTES(create_flow_group_in);
        struct mlx5_flow_group *fwd_grp;
        u32 *flow_group_in;
        u32 flow_index = 0;
        int ret = 0;

        if (MLX5_CAP_GEN(esw->dev, prio_tag_required)) {
                ret = esw_acl_egress_vlan_grp_create(esw, vport);
                if (ret)
                        return ret;

                flow_index++;
        }

        if (!mlx5_esw_acl_egress_fwd2vport_supported(esw))
                goto out;

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

        /* This group holds 1 FTE to forward all packets to other vport
         * when bond vports is supported.
         */
        MLX5_SET(create_flow_group_in, flow_group_in, start_flow_index, flow_index);
        MLX5_SET(create_flow_group_in, flow_group_in, end_flow_index, flow_index);
        fwd_grp = mlx5_create_flow_group(vport->egress.acl, flow_group_in);
        if (IS_ERR(fwd_grp)) {
                ret = PTR_ERR(fwd_grp);
                esw_warn(esw->dev,
                         "Failed to create vport[%d] egress fwd2vport flow group, err(%d)\n",
                         vport->vport, ret);
                kvfree(flow_group_in);
                goto fwd_grp_err;
        }
        vport->egress.offloads.fwd_grp = fwd_grp;
        kvfree(flow_group_in);
        return 0;

fwd_grp_err:
        esw_acl_egress_vlan_grp_destroy(vport);
out:
        return ret;
}

static void esw_acl_egress_ofld_groups_destroy(struct mlx5_vport *vport)
{
        if (!IS_ERR_OR_NULL(vport->egress.offloads.fwd_grp)) {
                mlx5_destroy_flow_group(vport->egress.offloads.fwd_grp);
                vport->egress.offloads.fwd_grp = NULL;
        }

        if (!IS_ERR_OR_NULL(vport->egress.offloads.bounce_grp)) {
                mlx5_destroy_flow_group(vport->egress.offloads.bounce_grp);
                vport->egress.offloads.bounce_grp = NULL;
        }

        esw_acl_egress_vlan_grp_destroy(vport);
}

static bool esw_acl_egress_needed(struct mlx5_eswitch *esw, u16 vport_num)
{
        return mlx5_eswitch_is_vf_vport(esw, vport_num) || mlx5_esw_is_sf_vport(esw, vport_num);
}

int esw_acl_egress_ofld_setup(struct mlx5_eswitch *esw, struct mlx5_vport *vport)
{
        int table_size = 0;
        int err;

        if (!mlx5_esw_acl_egress_fwd2vport_supported(esw) &&
            !MLX5_CAP_GEN(esw->dev, prio_tag_required))
                return 0;

        if (!esw_acl_egress_needed(esw, vport->vport))
                return 0;

        esw_acl_egress_ofld_rules_destroy(vport);

        if (mlx5_esw_acl_egress_fwd2vport_supported(esw))
                table_size++;
        if (MLX5_CAP_GEN(esw->dev, prio_tag_required))
                table_size++;
        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;
                return err;
        }
        vport->egress.type = VPORT_EGRESS_ACL_TYPE_DEFAULT;

        err = esw_acl_egress_ofld_groups_create(esw, vport);
        if (err)
                goto group_err;

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

        err = esw_acl_egress_ofld_rules_create(esw, vport, NULL);
        if (err)
                goto rules_err;

        return 0;

rules_err:
        esw_acl_egress_ofld_groups_destroy(vport);
group_err:
        esw_acl_egress_table_destroy(vport);
        return err;
}

void esw_acl_egress_ofld_cleanup(struct mlx5_vport *vport)
{
        esw_acl_egress_ofld_rules_destroy(vport);
        esw_acl_egress_ofld_groups_destroy(vport);
        esw_acl_egress_table_destroy(vport);
}

int mlx5_esw_acl_egress_vport_bond(struct mlx5_eswitch *esw, u16 active_vport_num,
                                   u16 passive_vport_num)
{
        struct mlx5_vport *passive_vport = mlx5_eswitch_get_vport(esw, passive_vport_num);
        struct mlx5_vport *active_vport = mlx5_eswitch_get_vport(esw, active_vport_num);
        struct mlx5_flow_destination fwd_dest = {};

        if (IS_ERR(active_vport))
                return PTR_ERR(active_vport);
        if (IS_ERR(passive_vport))
                return PTR_ERR(passive_vport);

        /* Cleanup and recreate rules WITHOUT fwd2vport of active vport */
        esw_acl_egress_ofld_rules_destroy(active_vport);
        esw_acl_egress_ofld_rules_create(esw, active_vport, NULL);

        /* Cleanup and recreate all rules + fwd2vport rule of passive vport to forward */
        esw_acl_egress_ofld_rules_destroy(passive_vport);
        fwd_dest.type = MLX5_FLOW_DESTINATION_TYPE_VPORT;
        fwd_dest.vport.num = active_vport_num;
        fwd_dest.vport.vhca_id = MLX5_CAP_GEN(esw->dev, vhca_id);
        fwd_dest.vport.flags = MLX5_FLOW_DEST_VPORT_VHCA_ID;

        return esw_acl_egress_ofld_rules_create(esw, passive_vport, &fwd_dest);
}

int mlx5_esw_acl_egress_vport_unbond(struct mlx5_eswitch *esw, u16 vport_num)
{
        struct mlx5_vport *vport = mlx5_eswitch_get_vport(esw, vport_num);

        if (IS_ERR(vport))
                return PTR_ERR(vport);

        esw_acl_egress_ofld_rules_destroy(vport);
        return esw_acl_egress_ofld_rules_create(esw, vport, NULL);
}