| // 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; |
| } |
| |
| 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); |
| } |
| |
| 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; |
| } |
| esw_acl_egress_vlan_grp_destroy(vport); |
| } |
| |
| static bool esw_acl_egress_needed(const struct mlx5_eswitch *esw, u16 vport_num) |
| { |
| return mlx5_eswitch_is_vf_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->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; |
| } |
| |
| 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); |
| } |