| // SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB |
| /* Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. */ |
| |
| #include "lib/devcom.h" |
| #include "bridge.h" |
| #include "eswitch.h" |
| #include "bridge_priv.h" |
| #include "diag/bridge_tracepoint.h" |
| |
| static const struct rhashtable_params mdb_ht_params = { |
| .key_offset = offsetof(struct mlx5_esw_bridge_mdb_entry, key), |
| .key_len = sizeof(struct mlx5_esw_bridge_mdb_key), |
| .head_offset = offsetof(struct mlx5_esw_bridge_mdb_entry, ht_node), |
| .automatic_shrinking = true, |
| }; |
| |
| int mlx5_esw_bridge_mdb_init(struct mlx5_esw_bridge *bridge) |
| { |
| INIT_LIST_HEAD(&bridge->mdb_list); |
| return rhashtable_init(&bridge->mdb_ht, &mdb_ht_params); |
| } |
| |
| void mlx5_esw_bridge_mdb_cleanup(struct mlx5_esw_bridge *bridge) |
| { |
| rhashtable_destroy(&bridge->mdb_ht); |
| } |
| |
| static struct mlx5_esw_bridge_port * |
| mlx5_esw_bridge_mdb_port_lookup(struct mlx5_esw_bridge_port *port, |
| struct mlx5_esw_bridge_mdb_entry *entry) |
| { |
| return xa_load(&entry->ports, mlx5_esw_bridge_port_key(port)); |
| } |
| |
| static int mlx5_esw_bridge_mdb_port_insert(struct mlx5_esw_bridge_port *port, |
| struct mlx5_esw_bridge_mdb_entry *entry) |
| { |
| int err = xa_insert(&entry->ports, mlx5_esw_bridge_port_key(port), port, GFP_KERNEL); |
| |
| if (!err) |
| entry->num_ports++; |
| return err; |
| } |
| |
| static void mlx5_esw_bridge_mdb_port_remove(struct mlx5_esw_bridge_port *port, |
| struct mlx5_esw_bridge_mdb_entry *entry) |
| { |
| xa_erase(&entry->ports, mlx5_esw_bridge_port_key(port)); |
| entry->num_ports--; |
| } |
| |
| static struct mlx5_flow_handle * |
| mlx5_esw_bridge_mdb_flow_create(u16 esw_owner_vhca_id, struct mlx5_esw_bridge_mdb_entry *entry, |
| struct mlx5_esw_bridge *bridge) |
| { |
| struct mlx5_flow_act flow_act = { |
| .action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST, |
| .flags = FLOW_ACT_NO_APPEND | FLOW_ACT_IGNORE_FLOW_LEVEL, |
| }; |
| int num_dests = entry->num_ports, i = 0; |
| struct mlx5_flow_destination *dests; |
| struct mlx5_esw_bridge_port *port; |
| struct mlx5_flow_spec *rule_spec; |
| struct mlx5_flow_handle *handle; |
| u8 *dmac_v, *dmac_c; |
| unsigned long idx; |
| |
| rule_spec = kvzalloc(sizeof(*rule_spec), GFP_KERNEL); |
| if (!rule_spec) |
| return ERR_PTR(-ENOMEM); |
| |
| dests = kvcalloc(num_dests, sizeof(*dests), GFP_KERNEL); |
| if (!dests) { |
| kvfree(rule_spec); |
| return ERR_PTR(-ENOMEM); |
| } |
| |
| xa_for_each(&entry->ports, idx, port) { |
| dests[i].type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE; |
| dests[i].ft = port->mcast.ft; |
| if (port->vport_num == MLX5_VPORT_UPLINK) |
| dests[i].ft->flags |= MLX5_FLOW_TABLE_UPLINK_VPORT; |
| i++; |
| } |
| |
| rule_spec->flow_context.flags |= FLOW_CONTEXT_UPLINK_HAIRPIN_EN; |
| rule_spec->match_criteria_enable = MLX5_MATCH_OUTER_HEADERS; |
| dmac_v = MLX5_ADDR_OF(fte_match_param, rule_spec->match_value, outer_headers.dmac_47_16); |
| ether_addr_copy(dmac_v, entry->key.addr); |
| dmac_c = MLX5_ADDR_OF(fte_match_param, rule_spec->match_criteria, outer_headers.dmac_47_16); |
| eth_broadcast_addr(dmac_c); |
| |
| if (entry->key.vid) { |
| if (bridge->vlan_proto == ETH_P_8021Q) { |
| MLX5_SET_TO_ONES(fte_match_param, rule_spec->match_criteria, |
| outer_headers.cvlan_tag); |
| MLX5_SET_TO_ONES(fte_match_param, rule_spec->match_value, |
| outer_headers.cvlan_tag); |
| } else if (bridge->vlan_proto == ETH_P_8021AD) { |
| MLX5_SET_TO_ONES(fte_match_param, rule_spec->match_criteria, |
| outer_headers.svlan_tag); |
| MLX5_SET_TO_ONES(fte_match_param, rule_spec->match_value, |
| outer_headers.svlan_tag); |
| } |
| MLX5_SET_TO_ONES(fte_match_param, rule_spec->match_criteria, |
| outer_headers.first_vid); |
| MLX5_SET(fte_match_param, rule_spec->match_value, outer_headers.first_vid, |
| entry->key.vid); |
| } |
| |
| handle = mlx5_add_flow_rules(bridge->egress_ft, rule_spec, &flow_act, dests, num_dests); |
| |
| kvfree(dests); |
| kvfree(rule_spec); |
| return handle; |
| } |
| |
| static int |
| mlx5_esw_bridge_port_mdb_offload(struct mlx5_esw_bridge_port *port, |
| struct mlx5_esw_bridge_mdb_entry *entry) |
| { |
| struct mlx5_flow_handle *handle; |
| |
| handle = mlx5_esw_bridge_mdb_flow_create(port->esw_owner_vhca_id, entry, port->bridge); |
| if (entry->egress_handle) { |
| mlx5_del_flow_rules(entry->egress_handle); |
| entry->egress_handle = NULL; |
| } |
| if (IS_ERR(handle)) |
| return PTR_ERR(handle); |
| |
| entry->egress_handle = handle; |
| return 0; |
| } |
| |
| static struct mlx5_esw_bridge_mdb_entry * |
| mlx5_esw_bridge_mdb_lookup(struct mlx5_esw_bridge *bridge, |
| const unsigned char *addr, u16 vid) |
| { |
| struct mlx5_esw_bridge_mdb_key key = {}; |
| |
| ether_addr_copy(key.addr, addr); |
| key.vid = vid; |
| return rhashtable_lookup_fast(&bridge->mdb_ht, &key, mdb_ht_params); |
| } |
| |
| static struct mlx5_esw_bridge_mdb_entry * |
| mlx5_esw_bridge_port_mdb_entry_init(struct mlx5_esw_bridge_port *port, |
| const unsigned char *addr, u16 vid) |
| { |
| struct mlx5_esw_bridge *bridge = port->bridge; |
| struct mlx5_esw_bridge_mdb_entry *entry; |
| int err; |
| |
| entry = kvzalloc(sizeof(*entry), GFP_KERNEL); |
| if (!entry) |
| return ERR_PTR(-ENOMEM); |
| |
| ether_addr_copy(entry->key.addr, addr); |
| entry->key.vid = vid; |
| xa_init(&entry->ports); |
| err = rhashtable_insert_fast(&bridge->mdb_ht, &entry->ht_node, mdb_ht_params); |
| if (err) |
| goto err_ht_insert; |
| |
| list_add(&entry->list, &bridge->mdb_list); |
| |
| return entry; |
| |
| err_ht_insert: |
| xa_destroy(&entry->ports); |
| kvfree(entry); |
| return ERR_PTR(err); |
| } |
| |
| static void mlx5_esw_bridge_port_mdb_entry_cleanup(struct mlx5_esw_bridge *bridge, |
| struct mlx5_esw_bridge_mdb_entry *entry) |
| { |
| if (entry->egress_handle) |
| mlx5_del_flow_rules(entry->egress_handle); |
| list_del(&entry->list); |
| rhashtable_remove_fast(&bridge->mdb_ht, &entry->ht_node, mdb_ht_params); |
| xa_destroy(&entry->ports); |
| kvfree(entry); |
| } |
| |
| int mlx5_esw_bridge_port_mdb_attach(struct net_device *dev, struct mlx5_esw_bridge_port *port, |
| const unsigned char *addr, u16 vid) |
| { |
| struct mlx5_esw_bridge *bridge = port->bridge; |
| struct mlx5_esw_bridge_mdb_entry *entry; |
| int err; |
| |
| if (!(bridge->flags & MLX5_ESW_BRIDGE_MCAST_FLAG)) |
| return -EOPNOTSUPP; |
| |
| entry = mlx5_esw_bridge_mdb_lookup(bridge, addr, vid); |
| if (entry) { |
| if (mlx5_esw_bridge_mdb_port_lookup(port, entry)) { |
| esw_warn(bridge->br_offloads->esw->dev, "MDB attach entry is already attached to port (MAC=%pM,vid=%u,vport=%u)\n", |
| addr, vid, port->vport_num); |
| return 0; |
| } |
| } else { |
| entry = mlx5_esw_bridge_port_mdb_entry_init(port, addr, vid); |
| if (IS_ERR(entry)) { |
| err = PTR_ERR(entry); |
| esw_warn(bridge->br_offloads->esw->dev, "MDB attach failed to init entry (MAC=%pM,vid=%u,vport=%u,err=%d)\n", |
| addr, vid, port->vport_num, err); |
| return err; |
| } |
| } |
| |
| err = mlx5_esw_bridge_mdb_port_insert(port, entry); |
| if (err) { |
| if (!entry->num_ports) |
| mlx5_esw_bridge_port_mdb_entry_cleanup(bridge, entry); /* new mdb entry */ |
| esw_warn(bridge->br_offloads->esw->dev, |
| "MDB attach failed to insert port (MAC=%pM,vid=%u,vport=%u,err=%d)\n", |
| addr, vid, port->vport_num, err); |
| return err; |
| } |
| |
| err = mlx5_esw_bridge_port_mdb_offload(port, entry); |
| if (err) |
| /* Single mdb can be used by multiple ports, so just log the |
| * error and continue. |
| */ |
| esw_warn(bridge->br_offloads->esw->dev, "MDB attach failed to offload (MAC=%pM,vid=%u,vport=%u,err=%d)\n", |
| addr, vid, port->vport_num, err); |
| |
| trace_mlx5_esw_bridge_port_mdb_attach(dev, entry); |
| return 0; |
| } |
| |
| static void mlx5_esw_bridge_port_mdb_entry_detach(struct mlx5_esw_bridge_port *port, |
| struct mlx5_esw_bridge_mdb_entry *entry) |
| { |
| struct mlx5_esw_bridge *bridge = port->bridge; |
| int err; |
| |
| mlx5_esw_bridge_mdb_port_remove(port, entry); |
| if (!entry->num_ports) { |
| mlx5_esw_bridge_port_mdb_entry_cleanup(bridge, entry); |
| return; |
| } |
| |
| err = mlx5_esw_bridge_port_mdb_offload(port, entry); |
| if (err) |
| /* Single mdb can be used by multiple ports, so just log the |
| * error and continue. |
| */ |
| esw_warn(bridge->br_offloads->esw->dev, "MDB detach failed to offload (MAC=%pM,vid=%u,vport=%u)\n", |
| entry->key.addr, entry->key.vid, port->vport_num); |
| } |
| |
| void mlx5_esw_bridge_port_mdb_detach(struct net_device *dev, struct mlx5_esw_bridge_port *port, |
| const unsigned char *addr, u16 vid) |
| { |
| struct mlx5_esw_bridge *bridge = port->bridge; |
| struct mlx5_esw_bridge_mdb_entry *entry; |
| |
| entry = mlx5_esw_bridge_mdb_lookup(bridge, addr, vid); |
| if (!entry) { |
| esw_debug(bridge->br_offloads->esw->dev, |
| "MDB detach entry not found (MAC=%pM,vid=%u,vport=%u)\n", |
| addr, vid, port->vport_num); |
| return; |
| } |
| |
| if (!mlx5_esw_bridge_mdb_port_lookup(port, entry)) { |
| esw_debug(bridge->br_offloads->esw->dev, |
| "MDB detach entry not attached to the port (MAC=%pM,vid=%u,vport=%u)\n", |
| addr, vid, port->vport_num); |
| return; |
| } |
| |
| trace_mlx5_esw_bridge_port_mdb_detach(dev, entry); |
| mlx5_esw_bridge_port_mdb_entry_detach(port, entry); |
| } |
| |
| void mlx5_esw_bridge_port_mdb_vlan_flush(struct mlx5_esw_bridge_port *port, |
| struct mlx5_esw_bridge_vlan *vlan) |
| { |
| struct mlx5_esw_bridge *bridge = port->bridge; |
| struct mlx5_esw_bridge_mdb_entry *entry, *tmp; |
| |
| list_for_each_entry_safe(entry, tmp, &bridge->mdb_list, list) |
| if (entry->key.vid == vlan->vid && mlx5_esw_bridge_mdb_port_lookup(port, entry)) |
| mlx5_esw_bridge_port_mdb_entry_detach(port, entry); |
| } |
| |
| static void mlx5_esw_bridge_port_mdb_flush(struct mlx5_esw_bridge_port *port) |
| { |
| struct mlx5_esw_bridge *bridge = port->bridge; |
| struct mlx5_esw_bridge_mdb_entry *entry, *tmp; |
| |
| list_for_each_entry_safe(entry, tmp, &bridge->mdb_list, list) |
| if (mlx5_esw_bridge_mdb_port_lookup(port, entry)) |
| mlx5_esw_bridge_port_mdb_entry_detach(port, entry); |
| } |
| |
| void mlx5_esw_bridge_mdb_flush(struct mlx5_esw_bridge *bridge) |
| { |
| struct mlx5_esw_bridge_mdb_entry *entry, *tmp; |
| |
| list_for_each_entry_safe(entry, tmp, &bridge->mdb_list, list) |
| mlx5_esw_bridge_port_mdb_entry_cleanup(bridge, entry); |
| } |
| static int mlx5_esw_bridge_port_mcast_fts_init(struct mlx5_esw_bridge_port *port, |
| struct mlx5_esw_bridge *bridge) |
| { |
| struct mlx5_eswitch *esw = bridge->br_offloads->esw; |
| struct mlx5_flow_table *mcast_ft; |
| |
| mcast_ft = mlx5_esw_bridge_table_create(MLX5_ESW_BRIDGE_MCAST_TABLE_SIZE, |
| MLX5_ESW_BRIDGE_LEVEL_MCAST_TABLE, |
| esw); |
| if (IS_ERR(mcast_ft)) |
| return PTR_ERR(mcast_ft); |
| |
| port->mcast.ft = mcast_ft; |
| return 0; |
| } |
| |
| static void mlx5_esw_bridge_port_mcast_fts_cleanup(struct mlx5_esw_bridge_port *port) |
| { |
| if (port->mcast.ft) |
| mlx5_destroy_flow_table(port->mcast.ft); |
| port->mcast.ft = NULL; |
| } |
| |
| static struct mlx5_flow_group * |
| mlx5_esw_bridge_mcast_filter_fg_create(struct mlx5_eswitch *esw, |
| struct mlx5_flow_table *mcast_ft) |
| { |
| int inlen = MLX5_ST_SZ_BYTES(create_flow_group_in); |
| struct mlx5_flow_group *fg; |
| u32 *in, *match; |
| |
| in = kvzalloc(inlen, GFP_KERNEL); |
| if (!in) |
| return ERR_PTR(-ENOMEM); |
| |
| MLX5_SET(create_flow_group_in, in, match_criteria_enable, MLX5_MATCH_MISC_PARAMETERS_2); |
| match = MLX5_ADDR_OF(create_flow_group_in, in, match_criteria); |
| |
| MLX5_SET(fte_match_param, match, misc_parameters_2.metadata_reg_c_0, |
| mlx5_eswitch_get_vport_metadata_mask()); |
| |
| MLX5_SET(create_flow_group_in, in, start_flow_index, |
| MLX5_ESW_BRIDGE_MCAST_TABLE_FILTER_GRP_IDX_FROM); |
| MLX5_SET(create_flow_group_in, in, end_flow_index, |
| MLX5_ESW_BRIDGE_MCAST_TABLE_FILTER_GRP_IDX_TO); |
| |
| fg = mlx5_create_flow_group(mcast_ft, in); |
| kvfree(in); |
| if (IS_ERR(fg)) |
| esw_warn(esw->dev, |
| "Failed to create filter flow group for bridge mcast table (err=%pe)\n", |
| fg); |
| |
| return fg; |
| } |
| |
| static struct mlx5_flow_group * |
| mlx5_esw_bridge_mcast_vlan_proto_fg_create(unsigned int from, unsigned int to, u16 vlan_proto, |
| struct mlx5_eswitch *esw, |
| struct mlx5_flow_table *mcast_ft) |
| { |
| int inlen = MLX5_ST_SZ_BYTES(create_flow_group_in); |
| struct mlx5_flow_group *fg; |
| u32 *in, *match; |
| |
| in = kvzalloc(inlen, GFP_KERNEL); |
| if (!in) |
| return ERR_PTR(-ENOMEM); |
| |
| MLX5_SET(create_flow_group_in, in, match_criteria_enable, MLX5_MATCH_OUTER_HEADERS); |
| match = MLX5_ADDR_OF(create_flow_group_in, in, match_criteria); |
| |
| if (vlan_proto == ETH_P_8021Q) |
| MLX5_SET_TO_ONES(fte_match_param, match, outer_headers.cvlan_tag); |
| else if (vlan_proto == ETH_P_8021AD) |
| MLX5_SET_TO_ONES(fte_match_param, match, outer_headers.svlan_tag); |
| MLX5_SET_TO_ONES(fte_match_param, match, outer_headers.first_vid); |
| |
| MLX5_SET(create_flow_group_in, in, start_flow_index, from); |
| MLX5_SET(create_flow_group_in, in, end_flow_index, to); |
| |
| fg = mlx5_create_flow_group(mcast_ft, in); |
| kvfree(in); |
| if (IS_ERR(fg)) |
| esw_warn(esw->dev, |
| "Failed to create VLAN(proto=%x) flow group for bridge mcast table (err=%pe)\n", |
| vlan_proto, fg); |
| |
| return fg; |
| } |
| |
| static struct mlx5_flow_group * |
| mlx5_esw_bridge_mcast_vlan_fg_create(struct mlx5_eswitch *esw, struct mlx5_flow_table *mcast_ft) |
| { |
| unsigned int from = MLX5_ESW_BRIDGE_MCAST_TABLE_VLAN_GRP_IDX_FROM; |
| unsigned int to = MLX5_ESW_BRIDGE_MCAST_TABLE_VLAN_GRP_IDX_TO; |
| |
| return mlx5_esw_bridge_mcast_vlan_proto_fg_create(from, to, ETH_P_8021Q, esw, mcast_ft); |
| } |
| |
| static struct mlx5_flow_group * |
| mlx5_esw_bridge_mcast_qinq_fg_create(struct mlx5_eswitch *esw, |
| struct mlx5_flow_table *mcast_ft) |
| { |
| unsigned int from = MLX5_ESW_BRIDGE_MCAST_TABLE_QINQ_GRP_IDX_FROM; |
| unsigned int to = MLX5_ESW_BRIDGE_MCAST_TABLE_QINQ_GRP_IDX_TO; |
| |
| return mlx5_esw_bridge_mcast_vlan_proto_fg_create(from, to, ETH_P_8021AD, esw, mcast_ft); |
| } |
| |
| static struct mlx5_flow_group * |
| mlx5_esw_bridge_mcast_fwd_fg_create(struct mlx5_eswitch *esw, |
| struct mlx5_flow_table *mcast_ft) |
| { |
| int inlen = MLX5_ST_SZ_BYTES(create_flow_group_in); |
| struct mlx5_flow_group *fg; |
| u32 *in; |
| |
| in = kvzalloc(inlen, GFP_KERNEL); |
| if (!in) |
| return ERR_PTR(-ENOMEM); |
| |
| MLX5_SET(create_flow_group_in, in, start_flow_index, |
| MLX5_ESW_BRIDGE_MCAST_TABLE_FWD_GRP_IDX_FROM); |
| MLX5_SET(create_flow_group_in, in, end_flow_index, |
| MLX5_ESW_BRIDGE_MCAST_TABLE_FWD_GRP_IDX_TO); |
| |
| fg = mlx5_create_flow_group(mcast_ft, in); |
| kvfree(in); |
| if (IS_ERR(fg)) |
| esw_warn(esw->dev, |
| "Failed to create forward flow group for bridge mcast table (err=%pe)\n", |
| fg); |
| |
| return fg; |
| } |
| |
| static int mlx5_esw_bridge_port_mcast_fgs_init(struct mlx5_esw_bridge_port *port) |
| { |
| struct mlx5_flow_group *fwd_fg, *qinq_fg, *vlan_fg, *filter_fg; |
| struct mlx5_eswitch *esw = port->bridge->br_offloads->esw; |
| struct mlx5_flow_table *mcast_ft = port->mcast.ft; |
| int err; |
| |
| filter_fg = mlx5_esw_bridge_mcast_filter_fg_create(esw, mcast_ft); |
| if (IS_ERR(filter_fg)) |
| return PTR_ERR(filter_fg); |
| |
| vlan_fg = mlx5_esw_bridge_mcast_vlan_fg_create(esw, mcast_ft); |
| if (IS_ERR(vlan_fg)) { |
| err = PTR_ERR(vlan_fg); |
| goto err_vlan_fg; |
| } |
| |
| qinq_fg = mlx5_esw_bridge_mcast_qinq_fg_create(esw, mcast_ft); |
| if (IS_ERR(qinq_fg)) { |
| err = PTR_ERR(qinq_fg); |
| goto err_qinq_fg; |
| } |
| |
| fwd_fg = mlx5_esw_bridge_mcast_fwd_fg_create(esw, mcast_ft); |
| if (IS_ERR(fwd_fg)) { |
| err = PTR_ERR(fwd_fg); |
| goto err_fwd_fg; |
| } |
| |
| port->mcast.filter_fg = filter_fg; |
| port->mcast.vlan_fg = vlan_fg; |
| port->mcast.qinq_fg = qinq_fg; |
| port->mcast.fwd_fg = fwd_fg; |
| |
| return 0; |
| |
| err_fwd_fg: |
| mlx5_destroy_flow_group(qinq_fg); |
| err_qinq_fg: |
| mlx5_destroy_flow_group(vlan_fg); |
| err_vlan_fg: |
| mlx5_destroy_flow_group(filter_fg); |
| return err; |
| } |
| |
| static void mlx5_esw_bridge_port_mcast_fgs_cleanup(struct mlx5_esw_bridge_port *port) |
| { |
| if (port->mcast.fwd_fg) |
| mlx5_destroy_flow_group(port->mcast.fwd_fg); |
| port->mcast.fwd_fg = NULL; |
| if (port->mcast.qinq_fg) |
| mlx5_destroy_flow_group(port->mcast.qinq_fg); |
| port->mcast.qinq_fg = NULL; |
| if (port->mcast.vlan_fg) |
| mlx5_destroy_flow_group(port->mcast.vlan_fg); |
| port->mcast.vlan_fg = NULL; |
| if (port->mcast.filter_fg) |
| mlx5_destroy_flow_group(port->mcast.filter_fg); |
| port->mcast.filter_fg = NULL; |
| } |
| |
| static struct mlx5_flow_handle * |
| mlx5_esw_bridge_mcast_flow_with_esw_create(struct mlx5_esw_bridge_port *port, |
| struct mlx5_eswitch *esw) |
| { |
| struct mlx5_flow_act flow_act = { |
| .action = MLX5_FLOW_CONTEXT_ACTION_DROP, |
| .flags = FLOW_ACT_NO_APPEND, |
| }; |
| struct mlx5_flow_spec *rule_spec; |
| struct mlx5_flow_handle *handle; |
| |
| rule_spec = kvzalloc(sizeof(*rule_spec), GFP_KERNEL); |
| if (!rule_spec) |
| return ERR_PTR(-ENOMEM); |
| |
| rule_spec->match_criteria_enable = MLX5_MATCH_MISC_PARAMETERS_2; |
| |
| MLX5_SET(fte_match_param, rule_spec->match_criteria, |
| misc_parameters_2.metadata_reg_c_0, mlx5_eswitch_get_vport_metadata_mask()); |
| MLX5_SET(fte_match_param, rule_spec->match_value, misc_parameters_2.metadata_reg_c_0, |
| mlx5_eswitch_get_vport_metadata_for_match(esw, port->vport_num)); |
| |
| handle = mlx5_add_flow_rules(port->mcast.ft, rule_spec, &flow_act, NULL, 0); |
| |
| kvfree(rule_spec); |
| return handle; |
| } |
| |
| static struct mlx5_flow_handle * |
| mlx5_esw_bridge_mcast_filter_flow_create(struct mlx5_esw_bridge_port *port) |
| { |
| return mlx5_esw_bridge_mcast_flow_with_esw_create(port, port->bridge->br_offloads->esw); |
| } |
| |
| static struct mlx5_flow_handle * |
| mlx5_esw_bridge_mcast_filter_flow_peer_create(struct mlx5_esw_bridge_port *port) |
| { |
| struct mlx5_devcom_comp_dev *devcom = port->bridge->br_offloads->esw->devcom, *pos; |
| struct mlx5_eswitch *tmp, *peer_esw = NULL; |
| static struct mlx5_flow_handle *handle; |
| |
| if (!mlx5_devcom_for_each_peer_begin(devcom)) |
| return ERR_PTR(-ENODEV); |
| |
| mlx5_devcom_for_each_peer_entry(devcom, tmp, pos) { |
| if (mlx5_esw_is_owner(tmp, port->vport_num, port->esw_owner_vhca_id)) { |
| peer_esw = tmp; |
| break; |
| } |
| } |
| |
| if (!peer_esw) { |
| handle = ERR_PTR(-ENODEV); |
| goto out; |
| } |
| |
| handle = mlx5_esw_bridge_mcast_flow_with_esw_create(port, peer_esw); |
| |
| out: |
| mlx5_devcom_for_each_peer_end(devcom); |
| return handle; |
| } |
| |
| static struct mlx5_flow_handle * |
| mlx5_esw_bridge_mcast_vlan_flow_create(u16 vlan_proto, struct mlx5_esw_bridge_port *port, |
| struct mlx5_esw_bridge_vlan *vlan) |
| { |
| struct mlx5_flow_act flow_act = { |
| .action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST, |
| .flags = FLOW_ACT_NO_APPEND, |
| }; |
| struct mlx5_flow_destination dest = { |
| .type = MLX5_FLOW_DESTINATION_TYPE_VPORT, |
| .vport.num = port->vport_num, |
| }; |
| struct mlx5_esw_bridge *bridge = port->bridge; |
| struct mlx5_flow_spec *rule_spec; |
| struct mlx5_flow_handle *handle; |
| |
| rule_spec = kvzalloc(sizeof(*rule_spec), GFP_KERNEL); |
| if (!rule_spec) |
| return ERR_PTR(-ENOMEM); |
| |
| rule_spec->flow_context.flags |= FLOW_CONTEXT_UPLINK_HAIRPIN_EN; |
| rule_spec->match_criteria_enable = MLX5_MATCH_OUTER_HEADERS; |
| |
| flow_act.action |= MLX5_FLOW_CONTEXT_ACTION_PACKET_REFORMAT; |
| flow_act.pkt_reformat = vlan->pkt_reformat_pop; |
| |
| if (vlan_proto == ETH_P_8021Q) { |
| MLX5_SET_TO_ONES(fte_match_param, rule_spec->match_criteria, |
| outer_headers.cvlan_tag); |
| MLX5_SET_TO_ONES(fte_match_param, rule_spec->match_value, |
| outer_headers.cvlan_tag); |
| } else if (vlan_proto == ETH_P_8021AD) { |
| MLX5_SET_TO_ONES(fte_match_param, rule_spec->match_criteria, |
| outer_headers.svlan_tag); |
| MLX5_SET_TO_ONES(fte_match_param, rule_spec->match_value, |
| outer_headers.svlan_tag); |
| } |
| MLX5_SET_TO_ONES(fte_match_param, rule_spec->match_criteria, outer_headers.first_vid); |
| MLX5_SET(fte_match_param, rule_spec->match_value, outer_headers.first_vid, vlan->vid); |
| |
| if (MLX5_CAP_ESW(bridge->br_offloads->esw->dev, merged_eswitch)) { |
| dest.vport.flags = MLX5_FLOW_DEST_VPORT_VHCA_ID; |
| dest.vport.vhca_id = port->esw_owner_vhca_id; |
| } |
| handle = mlx5_add_flow_rules(port->mcast.ft, rule_spec, &flow_act, &dest, 1); |
| |
| kvfree(rule_spec); |
| return handle; |
| } |
| |
| int mlx5_esw_bridge_vlan_mcast_init(u16 vlan_proto, struct mlx5_esw_bridge_port *port, |
| struct mlx5_esw_bridge_vlan *vlan) |
| { |
| struct mlx5_flow_handle *handle; |
| |
| if (!(port->bridge->flags & MLX5_ESW_BRIDGE_MCAST_FLAG)) |
| return 0; |
| |
| handle = mlx5_esw_bridge_mcast_vlan_flow_create(vlan_proto, port, vlan); |
| if (IS_ERR(handle)) |
| return PTR_ERR(handle); |
| |
| vlan->mcast_handle = handle; |
| return 0; |
| } |
| |
| void mlx5_esw_bridge_vlan_mcast_cleanup(struct mlx5_esw_bridge_vlan *vlan) |
| { |
| if (vlan->mcast_handle) |
| mlx5_del_flow_rules(vlan->mcast_handle); |
| vlan->mcast_handle = NULL; |
| } |
| |
| static struct mlx5_flow_handle * |
| mlx5_esw_bridge_mcast_fwd_flow_create(struct mlx5_esw_bridge_port *port) |
| { |
| struct mlx5_flow_act flow_act = { |
| .action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST, |
| .flags = FLOW_ACT_NO_APPEND, |
| }; |
| struct mlx5_flow_destination dest = { |
| .type = MLX5_FLOW_DESTINATION_TYPE_VPORT, |
| .vport.num = port->vport_num, |
| }; |
| struct mlx5_esw_bridge *bridge = port->bridge; |
| struct mlx5_flow_spec *rule_spec; |
| struct mlx5_flow_handle *handle; |
| |
| rule_spec = kvzalloc(sizeof(*rule_spec), GFP_KERNEL); |
| if (!rule_spec) |
| return ERR_PTR(-ENOMEM); |
| |
| if (MLX5_CAP_ESW(bridge->br_offloads->esw->dev, merged_eswitch)) { |
| dest.vport.flags = MLX5_FLOW_DEST_VPORT_VHCA_ID; |
| dest.vport.vhca_id = port->esw_owner_vhca_id; |
| } |
| rule_spec->flow_context.flags |= FLOW_CONTEXT_UPLINK_HAIRPIN_EN; |
| handle = mlx5_add_flow_rules(port->mcast.ft, rule_spec, &flow_act, &dest, 1); |
| |
| kvfree(rule_spec); |
| return handle; |
| } |
| |
| static int mlx5_esw_bridge_port_mcast_fhs_init(struct mlx5_esw_bridge_port *port) |
| { |
| struct mlx5_flow_handle *filter_handle, *fwd_handle; |
| struct mlx5_esw_bridge_vlan *vlan, *failed; |
| unsigned long index; |
| int err; |
| |
| |
| filter_handle = (port->flags & MLX5_ESW_BRIDGE_PORT_FLAG_PEER) ? |
| mlx5_esw_bridge_mcast_filter_flow_peer_create(port) : |
| mlx5_esw_bridge_mcast_filter_flow_create(port); |
| if (IS_ERR(filter_handle)) |
| return PTR_ERR(filter_handle); |
| |
| fwd_handle = mlx5_esw_bridge_mcast_fwd_flow_create(port); |
| if (IS_ERR(fwd_handle)) { |
| err = PTR_ERR(fwd_handle); |
| goto err_fwd; |
| } |
| |
| xa_for_each(&port->vlans, index, vlan) { |
| err = mlx5_esw_bridge_vlan_mcast_init(port->bridge->vlan_proto, port, vlan); |
| if (err) { |
| failed = vlan; |
| goto err_vlan; |
| } |
| } |
| |
| port->mcast.filter_handle = filter_handle; |
| port->mcast.fwd_handle = fwd_handle; |
| |
| return 0; |
| |
| err_vlan: |
| xa_for_each(&port->vlans, index, vlan) { |
| if (vlan == failed) |
| break; |
| |
| mlx5_esw_bridge_vlan_mcast_cleanup(vlan); |
| } |
| mlx5_del_flow_rules(fwd_handle); |
| err_fwd: |
| mlx5_del_flow_rules(filter_handle); |
| return err; |
| } |
| |
| static void mlx5_esw_bridge_port_mcast_fhs_cleanup(struct mlx5_esw_bridge_port *port) |
| { |
| struct mlx5_esw_bridge_vlan *vlan; |
| unsigned long index; |
| |
| xa_for_each(&port->vlans, index, vlan) |
| mlx5_esw_bridge_vlan_mcast_cleanup(vlan); |
| |
| if (port->mcast.fwd_handle) |
| mlx5_del_flow_rules(port->mcast.fwd_handle); |
| port->mcast.fwd_handle = NULL; |
| if (port->mcast.filter_handle) |
| mlx5_del_flow_rules(port->mcast.filter_handle); |
| port->mcast.filter_handle = NULL; |
| } |
| |
| int mlx5_esw_bridge_port_mcast_init(struct mlx5_esw_bridge_port *port) |
| { |
| struct mlx5_esw_bridge *bridge = port->bridge; |
| int err; |
| |
| if (!(bridge->flags & MLX5_ESW_BRIDGE_MCAST_FLAG)) |
| return 0; |
| |
| err = mlx5_esw_bridge_port_mcast_fts_init(port, bridge); |
| if (err) |
| return err; |
| |
| err = mlx5_esw_bridge_port_mcast_fgs_init(port); |
| if (err) |
| goto err_fgs; |
| |
| err = mlx5_esw_bridge_port_mcast_fhs_init(port); |
| if (err) |
| goto err_fhs; |
| return err; |
| |
| err_fhs: |
| mlx5_esw_bridge_port_mcast_fgs_cleanup(port); |
| err_fgs: |
| mlx5_esw_bridge_port_mcast_fts_cleanup(port); |
| return err; |
| } |
| |
| void mlx5_esw_bridge_port_mcast_cleanup(struct mlx5_esw_bridge_port *port) |
| { |
| mlx5_esw_bridge_port_mdb_flush(port); |
| mlx5_esw_bridge_port_mcast_fhs_cleanup(port); |
| mlx5_esw_bridge_port_mcast_fgs_cleanup(port); |
| mlx5_esw_bridge_port_mcast_fts_cleanup(port); |
| } |
| |
| static struct mlx5_flow_group * |
| mlx5_esw_bridge_ingress_igmp_fg_create(struct mlx5_eswitch *esw, |
| struct mlx5_flow_table *ingress_ft) |
| { |
| int inlen = MLX5_ST_SZ_BYTES(create_flow_group_in); |
| struct mlx5_flow_group *fg; |
| u32 *in, *match; |
| |
| in = kvzalloc(inlen, GFP_KERNEL); |
| if (!in) |
| return ERR_PTR(-ENOMEM); |
| |
| MLX5_SET(create_flow_group_in, in, match_criteria_enable, MLX5_MATCH_OUTER_HEADERS); |
| match = MLX5_ADDR_OF(create_flow_group_in, in, match_criteria); |
| |
| MLX5_SET_TO_ONES(fte_match_param, match, outer_headers.ip_version); |
| MLX5_SET_TO_ONES(fte_match_param, match, outer_headers.ip_protocol); |
| |
| MLX5_SET(create_flow_group_in, in, start_flow_index, |
| MLX5_ESW_BRIDGE_INGRESS_TABLE_IGMP_GRP_IDX_FROM); |
| MLX5_SET(create_flow_group_in, in, end_flow_index, |
| MLX5_ESW_BRIDGE_INGRESS_TABLE_IGMP_GRP_IDX_TO); |
| |
| fg = mlx5_create_flow_group(ingress_ft, in); |
| kvfree(in); |
| if (IS_ERR(fg)) |
| esw_warn(esw->dev, |
| "Failed to create IGMP flow group for bridge ingress table (err=%pe)\n", |
| fg); |
| |
| return fg; |
| } |
| |
| static struct mlx5_flow_group * |
| mlx5_esw_bridge_ingress_mld_fg_create(struct mlx5_eswitch *esw, |
| struct mlx5_flow_table *ingress_ft) |
| { |
| int inlen = MLX5_ST_SZ_BYTES(create_flow_group_in); |
| struct mlx5_flow_group *fg; |
| u32 *in, *match; |
| |
| if (!(MLX5_CAP_GEN(esw->dev, flex_parser_protocols) & MLX5_FLEX_PROTO_ICMPV6)) { |
| esw_warn(esw->dev, |
| "Can't create MLD flow group due to missing hardware ICMPv6 parsing support\n"); |
| return NULL; |
| } |
| |
| in = kvzalloc(inlen, GFP_KERNEL); |
| if (!in) |
| return ERR_PTR(-ENOMEM); |
| |
| MLX5_SET(create_flow_group_in, in, match_criteria_enable, |
| MLX5_MATCH_OUTER_HEADERS | MLX5_MATCH_MISC_PARAMETERS_3); |
| match = MLX5_ADDR_OF(create_flow_group_in, in, match_criteria); |
| |
| MLX5_SET_TO_ONES(fte_match_param, match, outer_headers.ip_version); |
| MLX5_SET_TO_ONES(fte_match_param, match, misc_parameters_3.icmpv6_type); |
| |
| MLX5_SET(create_flow_group_in, in, start_flow_index, |
| MLX5_ESW_BRIDGE_INGRESS_TABLE_MLD_GRP_IDX_FROM); |
| MLX5_SET(create_flow_group_in, in, end_flow_index, |
| MLX5_ESW_BRIDGE_INGRESS_TABLE_MLD_GRP_IDX_TO); |
| |
| fg = mlx5_create_flow_group(ingress_ft, in); |
| kvfree(in); |
| if (IS_ERR(fg)) |
| esw_warn(esw->dev, |
| "Failed to create MLD flow group for bridge ingress table (err=%pe)\n", |
| fg); |
| |
| return fg; |
| } |
| |
| static int |
| mlx5_esw_bridge_ingress_mcast_fgs_init(struct mlx5_esw_bridge_offloads *br_offloads) |
| { |
| struct mlx5_flow_table *ingress_ft = br_offloads->ingress_ft; |
| struct mlx5_eswitch *esw = br_offloads->esw; |
| struct mlx5_flow_group *igmp_fg, *mld_fg; |
| |
| igmp_fg = mlx5_esw_bridge_ingress_igmp_fg_create(esw, ingress_ft); |
| if (IS_ERR(igmp_fg)) |
| return PTR_ERR(igmp_fg); |
| |
| mld_fg = mlx5_esw_bridge_ingress_mld_fg_create(esw, ingress_ft); |
| if (IS_ERR(mld_fg)) { |
| mlx5_destroy_flow_group(igmp_fg); |
| return PTR_ERR(mld_fg); |
| } |
| |
| br_offloads->ingress_igmp_fg = igmp_fg; |
| br_offloads->ingress_mld_fg = mld_fg; |
| return 0; |
| } |
| |
| static void |
| mlx5_esw_bridge_ingress_mcast_fgs_cleanup(struct mlx5_esw_bridge_offloads *br_offloads) |
| { |
| if (br_offloads->ingress_mld_fg) |
| mlx5_destroy_flow_group(br_offloads->ingress_mld_fg); |
| br_offloads->ingress_mld_fg = NULL; |
| if (br_offloads->ingress_igmp_fg) |
| mlx5_destroy_flow_group(br_offloads->ingress_igmp_fg); |
| br_offloads->ingress_igmp_fg = NULL; |
| } |
| |
| static struct mlx5_flow_handle * |
| mlx5_esw_bridge_ingress_igmp_fh_create(struct mlx5_flow_table *ingress_ft, |
| struct mlx5_flow_table *skip_ft) |
| { |
| struct mlx5_flow_destination dest = { |
| .type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE, |
| .ft = skip_ft, |
| }; |
| struct mlx5_flow_act flow_act = { |
| .action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST, |
| .flags = FLOW_ACT_NO_APPEND, |
| }; |
| struct mlx5_flow_spec *rule_spec; |
| struct mlx5_flow_handle *handle; |
| |
| rule_spec = kvzalloc(sizeof(*rule_spec), GFP_KERNEL); |
| if (!rule_spec) |
| return ERR_PTR(-ENOMEM); |
| |
| rule_spec->match_criteria_enable = MLX5_MATCH_OUTER_HEADERS; |
| |
| MLX5_SET_TO_ONES(fte_match_param, rule_spec->match_criteria, outer_headers.ip_version); |
| MLX5_SET(fte_match_param, rule_spec->match_value, outer_headers.ip_version, 4); |
| MLX5_SET_TO_ONES(fte_match_param, rule_spec->match_criteria, outer_headers.ip_protocol); |
| MLX5_SET(fte_match_param, rule_spec->match_value, outer_headers.ip_protocol, IPPROTO_IGMP); |
| |
| handle = mlx5_add_flow_rules(ingress_ft, rule_spec, &flow_act, &dest, 1); |
| |
| kvfree(rule_spec); |
| return handle; |
| } |
| |
| static struct mlx5_flow_handle * |
| mlx5_esw_bridge_ingress_mld_fh_create(u8 type, struct mlx5_flow_table *ingress_ft, |
| struct mlx5_flow_table *skip_ft) |
| { |
| struct mlx5_flow_destination dest = { |
| .type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE, |
| .ft = skip_ft, |
| }; |
| struct mlx5_flow_act flow_act = { |
| .action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST, |
| .flags = FLOW_ACT_NO_APPEND, |
| }; |
| struct mlx5_flow_spec *rule_spec; |
| struct mlx5_flow_handle *handle; |
| |
| rule_spec = kvzalloc(sizeof(*rule_spec), GFP_KERNEL); |
| if (!rule_spec) |
| return ERR_PTR(-ENOMEM); |
| |
| rule_spec->match_criteria_enable = MLX5_MATCH_OUTER_HEADERS | MLX5_MATCH_MISC_PARAMETERS_3; |
| |
| MLX5_SET_TO_ONES(fte_match_param, rule_spec->match_criteria, outer_headers.ip_version); |
| MLX5_SET(fte_match_param, rule_spec->match_value, outer_headers.ip_version, 6); |
| MLX5_SET_TO_ONES(fte_match_param, rule_spec->match_criteria, misc_parameters_3.icmpv6_type); |
| MLX5_SET(fte_match_param, rule_spec->match_value, misc_parameters_3.icmpv6_type, type); |
| |
| handle = mlx5_add_flow_rules(ingress_ft, rule_spec, &flow_act, &dest, 1); |
| |
| kvfree(rule_spec); |
| return handle; |
| } |
| |
| static int |
| mlx5_esw_bridge_ingress_mcast_fhs_create(struct mlx5_esw_bridge_offloads *br_offloads) |
| { |
| struct mlx5_flow_handle *igmp_handle, *mld_query_handle, *mld_report_handle, |
| *mld_done_handle; |
| struct mlx5_flow_table *ingress_ft = br_offloads->ingress_ft, |
| *skip_ft = br_offloads->skip_ft; |
| int err; |
| |
| igmp_handle = mlx5_esw_bridge_ingress_igmp_fh_create(ingress_ft, skip_ft); |
| if (IS_ERR(igmp_handle)) |
| return PTR_ERR(igmp_handle); |
| |
| if (br_offloads->ingress_mld_fg) { |
| mld_query_handle = mlx5_esw_bridge_ingress_mld_fh_create(ICMPV6_MGM_QUERY, |
| ingress_ft, |
| skip_ft); |
| if (IS_ERR(mld_query_handle)) { |
| err = PTR_ERR(mld_query_handle); |
| goto err_mld_query; |
| } |
| |
| mld_report_handle = mlx5_esw_bridge_ingress_mld_fh_create(ICMPV6_MGM_REPORT, |
| ingress_ft, |
| skip_ft); |
| if (IS_ERR(mld_report_handle)) { |
| err = PTR_ERR(mld_report_handle); |
| goto err_mld_report; |
| } |
| |
| mld_done_handle = mlx5_esw_bridge_ingress_mld_fh_create(ICMPV6_MGM_REDUCTION, |
| ingress_ft, |
| skip_ft); |
| if (IS_ERR(mld_done_handle)) { |
| err = PTR_ERR(mld_done_handle); |
| goto err_mld_done; |
| } |
| } else { |
| mld_query_handle = NULL; |
| mld_report_handle = NULL; |
| mld_done_handle = NULL; |
| } |
| |
| br_offloads->igmp_handle = igmp_handle; |
| br_offloads->mld_query_handle = mld_query_handle; |
| br_offloads->mld_report_handle = mld_report_handle; |
| br_offloads->mld_done_handle = mld_done_handle; |
| |
| return 0; |
| |
| err_mld_done: |
| mlx5_del_flow_rules(mld_report_handle); |
| err_mld_report: |
| mlx5_del_flow_rules(mld_query_handle); |
| err_mld_query: |
| mlx5_del_flow_rules(igmp_handle); |
| return err; |
| } |
| |
| static void |
| mlx5_esw_bridge_ingress_mcast_fhs_cleanup(struct mlx5_esw_bridge_offloads *br_offloads) |
| { |
| if (br_offloads->mld_done_handle) |
| mlx5_del_flow_rules(br_offloads->mld_done_handle); |
| br_offloads->mld_done_handle = NULL; |
| if (br_offloads->mld_report_handle) |
| mlx5_del_flow_rules(br_offloads->mld_report_handle); |
| br_offloads->mld_report_handle = NULL; |
| if (br_offloads->mld_query_handle) |
| mlx5_del_flow_rules(br_offloads->mld_query_handle); |
| br_offloads->mld_query_handle = NULL; |
| if (br_offloads->igmp_handle) |
| mlx5_del_flow_rules(br_offloads->igmp_handle); |
| br_offloads->igmp_handle = NULL; |
| } |
| |
| static int mlx5_esw_brige_mcast_init(struct mlx5_esw_bridge *bridge) |
| { |
| struct mlx5_esw_bridge_offloads *br_offloads = bridge->br_offloads; |
| struct mlx5_esw_bridge_port *port, *failed; |
| unsigned long i; |
| int err; |
| |
| xa_for_each(&br_offloads->ports, i, port) { |
| if (port->bridge != bridge) |
| continue; |
| |
| err = mlx5_esw_bridge_port_mcast_init(port); |
| if (err) { |
| failed = port; |
| goto err_port; |
| } |
| } |
| return 0; |
| |
| err_port: |
| xa_for_each(&br_offloads->ports, i, port) { |
| if (port == failed) |
| break; |
| if (port->bridge != bridge) |
| continue; |
| |
| mlx5_esw_bridge_port_mcast_cleanup(port); |
| } |
| return err; |
| } |
| |
| static void mlx5_esw_brige_mcast_cleanup(struct mlx5_esw_bridge *bridge) |
| { |
| struct mlx5_esw_bridge_offloads *br_offloads = bridge->br_offloads; |
| struct mlx5_esw_bridge_port *port; |
| unsigned long i; |
| |
| xa_for_each(&br_offloads->ports, i, port) { |
| if (port->bridge != bridge) |
| continue; |
| |
| mlx5_esw_bridge_port_mcast_cleanup(port); |
| } |
| } |
| |
| static int mlx5_esw_brige_mcast_global_enable(struct mlx5_esw_bridge_offloads *br_offloads) |
| { |
| int err; |
| |
| if (br_offloads->ingress_igmp_fg) |
| return 0; /* already enabled by another bridge */ |
| |
| err = mlx5_esw_bridge_ingress_mcast_fgs_init(br_offloads); |
| if (err) { |
| esw_warn(br_offloads->esw->dev, |
| "Failed to create global multicast flow groups (err=%d)\n", |
| err); |
| return err; |
| } |
| |
| err = mlx5_esw_bridge_ingress_mcast_fhs_create(br_offloads); |
| if (err) { |
| esw_warn(br_offloads->esw->dev, |
| "Failed to create global multicast flows (err=%d)\n", |
| err); |
| goto err_fhs; |
| } |
| |
| return 0; |
| |
| err_fhs: |
| mlx5_esw_bridge_ingress_mcast_fgs_cleanup(br_offloads); |
| return err; |
| } |
| |
| static void mlx5_esw_brige_mcast_global_disable(struct mlx5_esw_bridge_offloads *br_offloads) |
| { |
| struct mlx5_esw_bridge *br; |
| |
| list_for_each_entry(br, &br_offloads->bridges, list) { |
| /* Ingress table is global, so only disable snooping when all |
| * bridges on esw have multicast disabled. |
| */ |
| if (br->flags & MLX5_ESW_BRIDGE_MCAST_FLAG) |
| return; |
| } |
| |
| mlx5_esw_bridge_ingress_mcast_fhs_cleanup(br_offloads); |
| mlx5_esw_bridge_ingress_mcast_fgs_cleanup(br_offloads); |
| } |
| |
| int mlx5_esw_bridge_mcast_enable(struct mlx5_esw_bridge *bridge) |
| { |
| int err; |
| |
| err = mlx5_esw_brige_mcast_global_enable(bridge->br_offloads); |
| if (err) |
| return err; |
| |
| bridge->flags |= MLX5_ESW_BRIDGE_MCAST_FLAG; |
| |
| err = mlx5_esw_brige_mcast_init(bridge); |
| if (err) { |
| esw_warn(bridge->br_offloads->esw->dev, "Failed to enable multicast (err=%d)\n", |
| err); |
| bridge->flags &= ~MLX5_ESW_BRIDGE_MCAST_FLAG; |
| mlx5_esw_brige_mcast_global_disable(bridge->br_offloads); |
| } |
| return err; |
| } |
| |
| void mlx5_esw_bridge_mcast_disable(struct mlx5_esw_bridge *bridge) |
| { |
| mlx5_esw_brige_mcast_cleanup(bridge); |
| bridge->flags &= ~MLX5_ESW_BRIDGE_MCAST_FLAG; |
| mlx5_esw_brige_mcast_global_disable(bridge->br_offloads); |
| } |