Commit 58be2427 authored by David S. Miller's avatar David S. Miller

Merge branch 'mlxsw-offload-mc-flood'

Jiri Pirko says:

====================
mlxsw: Offload MC flood for unregister MC

Nogah says:

When multicast is enabled, the Linux bridge floods unregistered multicast
packets only to ports connected to a multicast router. Devices capable of
offloading the Linux bridge need to be made aware of such ports, for
proper flooding behavior.
On the other hand, when multicast is disabled, such packets should be
flooded to all ports. This patchset aims to fix that, by offloading
the multicast state and the list of multicast router ports.

The first 3 patches adds switchdev attributes to offload this data.
The rest of the patchset add implementation for handling this data in the
mlxsw driver.

The effects this data has on the MDB (namely, when the multicast is
disabled the MDB should be considered as invalid, and when it is enabled, a
packet that is flooded by it should also be flooded to the multicast
routers ports) is subject of future work.

Testing of this patchset included:
Sending 3 mc packets streams, LL, register and unregistered, and checking
that they reached only to the ports that should have received them.
The configs were:
mc disabled, mc without mc router ports and mc with fixed router port.
It was checked for vlan aware bridge, vlan unaware bridge and vlan unaware
bridge with another vlan unaware bridge on the same machine
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 94134bf8 90e0f0c1
......@@ -3073,10 +3073,17 @@ static int __mlxsw_sp_flood_init(struct mlxsw_core *mlxsw_core,
else
table_type = MLXSW_REG_SFGC_TABLE_TYPE_FID_OFFEST;
if (type == MLXSW_REG_SFGC_TYPE_UNKNOWN_UNICAST)
switch (type) {
case MLXSW_REG_SFGC_TYPE_UNKNOWN_UNICAST:
flood_table = MLXSW_SP_FLOOD_TABLE_UC;
else
flood_table = MLXSW_SP_FLOOD_TABLE_BM;
break;
case MLXSW_REG_SFGC_TYPE_UNREGISTERED_MULTICAST_IPV4:
case MLXSW_REG_SFGC_TYPE_UNREGISTERED_MULTICAST_IPV6:
flood_table = MLXSW_SP_FLOOD_TABLE_MC;
break;
default:
flood_table = MLXSW_SP_FLOOD_TABLE_BC;
}
mlxsw_reg_sfgc_pack(sfgc_pl, type, bridge_type, table_type,
flood_table);
......@@ -3270,9 +3277,9 @@ static struct mlxsw_config_profile mlxsw_sp_config_profile = {
.used_flood_tables = 1,
.used_flood_mode = 1,
.flood_mode = 3,
.max_fid_offset_flood_tables = 2,
.max_fid_offset_flood_tables = 3,
.fid_offset_flood_table_size = VLAN_N_VID - 1,
.max_fid_flood_tables = 2,
.max_fid_flood_tables = 3,
.fid_flood_table_size = MLXSW_SP_VFID_MAX,
.used_max_ib_mc = 1,
.max_ib_mc = 0,
......@@ -3689,7 +3696,7 @@ static int mlxsw_sp_router_port_flood_set(struct mlxsw_sp *mlxsw_sp, u16 fid,
table_type = mlxsw_sp_flood_table_type_get(fid);
index = mlxsw_sp_flood_table_index_get(fid);
mlxsw_reg_sftr_pack(sftr_pl, MLXSW_SP_FLOOD_TABLE_BM, index, table_type,
mlxsw_reg_sftr_pack(sftr_pl, MLXSW_SP_FLOOD_TABLE_BC, index, table_type,
1, MLXSW_PORT_ROUTER_PORT, set);
err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(sftr), sftr_pl);
......@@ -4065,6 +4072,9 @@ static int mlxsw_sp_port_bridge_join(struct mlxsw_sp_port *mlxsw_sp_port,
mlxsw_sp_port->learning = 1;
mlxsw_sp_port->learning_sync = 1;
mlxsw_sp_port->uc_flood = 1;
mlxsw_sp_port->mc_flood = 1;
mlxsw_sp_port->mc_router = 0;
mlxsw_sp_port->mc_disabled = 1;
mlxsw_sp_port->bridged = 1;
return 0;
......@@ -4081,6 +4091,8 @@ static void mlxsw_sp_port_bridge_leave(struct mlxsw_sp_port *mlxsw_sp_port)
mlxsw_sp_port->learning = 0;
mlxsw_sp_port->learning_sync = 0;
mlxsw_sp_port->uc_flood = 0;
mlxsw_sp_port->mc_flood = 0;
mlxsw_sp_port->mc_router = 0;
mlxsw_sp_port->bridged = 0;
/* Add implicit VLAN interface in the device, so that untagged
......@@ -4743,6 +4755,9 @@ static int mlxsw_sp_vport_bridge_join(struct mlxsw_sp_port *mlxsw_sp_vport,
mlxsw_sp_vport->learning = 1;
mlxsw_sp_vport->learning_sync = 1;
mlxsw_sp_vport->uc_flood = 1;
mlxsw_sp_vport->mc_flood = 1;
mlxsw_sp_vport->mc_router = 0;
mlxsw_sp_vport->mc_disabled = 1;
mlxsw_sp_vport->bridged = 1;
return 0;
......@@ -4763,6 +4778,8 @@ static void mlxsw_sp_vport_bridge_leave(struct mlxsw_sp_port *mlxsw_sp_vport)
mlxsw_sp_vport->learning = 0;
mlxsw_sp_vport->learning_sync = 0;
mlxsw_sp_vport->uc_flood = 0;
mlxsw_sp_vport->mc_flood = 0;
mlxsw_sp_vport->mc_router = 0;
mlxsw_sp_vport->bridged = 0;
}
......
......@@ -55,7 +55,7 @@
#include "core_acl_flex_actions.h"
#define MLXSW_SP_VFID_BASE VLAN_N_VID
#define MLXSW_SP_VFID_MAX 6656 /* Bridged VLAN interfaces */
#define MLXSW_SP_VFID_MAX 1024 /* Bridged VLAN interfaces */
#define MLXSW_SP_RFID_BASE 15360
#define MLXSW_SP_INVALID_RIF 0xffff
......@@ -339,9 +339,12 @@ struct mlxsw_sp_port {
struct mlxsw_sp *mlxsw_sp;
u8 local_port;
u8 stp_state;
u8 learning:1,
u16 learning:1,
learning_sync:1,
uc_flood:1,
mc_flood:1,
mc_router:1,
mc_disabled:1,
bridged:1,
lagged:1,
split:1;
......@@ -509,7 +512,8 @@ mlxsw_sp_rif_find_by_dev(const struct mlxsw_sp *mlxsw_sp,
enum mlxsw_sp_flood_table {
MLXSW_SP_FLOOD_TABLE_UC,
MLXSW_SP_FLOOD_TABLE_BM,
MLXSW_SP_FLOOD_TABLE_BC,
MLXSW_SP_FLOOD_TABLE_MC,
};
int mlxsw_sp_buffers_init(struct mlxsw_sp *mlxsw_sp);
......
......@@ -71,8 +71,21 @@ mlxsw_sp_port_orig_get(struct net_device *dev,
struct mlxsw_sp_port *mlxsw_sp_port)
{
struct mlxsw_sp_port *mlxsw_sp_vport;
struct mlxsw_sp_fid *fid;
u16 vid;
if (netif_is_bridge_master(dev)) {
fid = mlxsw_sp_vfid_find(mlxsw_sp_port->mlxsw_sp,
dev);
if (fid) {
mlxsw_sp_vport =
mlxsw_sp_port_vport_find_by_fid(mlxsw_sp_port,
fid->fid);
WARN_ON(!mlxsw_sp_vport);
return mlxsw_sp_vport;
}
}
if (!is_vlan_dev(dev))
return mlxsw_sp_port;
......@@ -166,9 +179,10 @@ static int mlxsw_sp_port_attr_stp_state_set(struct mlxsw_sp_port *mlxsw_sp_port,
return mlxsw_sp_port_stp_state_set(mlxsw_sp_port, state);
}
static int __mlxsw_sp_port_flood_set(struct mlxsw_sp_port *mlxsw_sp_port,
u16 idx_begin, u16 idx_end, bool uc_set,
bool bm_set)
static int __mlxsw_sp_port_flood_table_set(struct mlxsw_sp_port *mlxsw_sp_port,
u16 idx_begin, u16 idx_end,
enum mlxsw_sp_flood_table table,
bool set)
{
struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
u16 local_port = mlxsw_sp_port->local_port;
......@@ -186,31 +200,48 @@ static int __mlxsw_sp_port_flood_set(struct mlxsw_sp_port *mlxsw_sp_port,
if (!sftr_pl)
return -ENOMEM;
mlxsw_reg_sftr_pack(sftr_pl, MLXSW_SP_FLOOD_TABLE_UC, idx_begin,
table_type, range, local_port, uc_set);
mlxsw_reg_sftr_pack(sftr_pl, table, idx_begin,
table_type, range, local_port, set);
err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(sftr), sftr_pl);
kfree(sftr_pl);
return err;
}
static int __mlxsw_sp_port_flood_set(struct mlxsw_sp_port *mlxsw_sp_port,
u16 idx_begin, u16 idx_end, bool uc_set,
bool bc_set, bool mc_set)
{
int err;
err = __mlxsw_sp_port_flood_table_set(mlxsw_sp_port, idx_begin, idx_end,
MLXSW_SP_FLOOD_TABLE_UC, uc_set);
if (err)
goto buffer_out;
return err;
mlxsw_reg_sftr_pack(sftr_pl, MLXSW_SP_FLOOD_TABLE_BM, idx_begin,
table_type, range, local_port, bm_set);
err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(sftr), sftr_pl);
err = __mlxsw_sp_port_flood_table_set(mlxsw_sp_port, idx_begin, idx_end,
MLXSW_SP_FLOOD_TABLE_BC, bc_set);
if (err)
goto err_flood_bm_set;
goto buffer_out;
err = __mlxsw_sp_port_flood_table_set(mlxsw_sp_port, idx_begin, idx_end,
MLXSW_SP_FLOOD_TABLE_MC, mc_set);
if (err)
goto err_flood_mc_set;
return 0;
err_flood_mc_set:
__mlxsw_sp_port_flood_table_set(mlxsw_sp_port, idx_begin, idx_end,
MLXSW_SP_FLOOD_TABLE_BC, !bc_set);
err_flood_bm_set:
mlxsw_reg_sftr_pack(sftr_pl, MLXSW_SP_FLOOD_TABLE_UC, idx_begin,
table_type, range, local_port, !uc_set);
mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(sftr), sftr_pl);
buffer_out:
kfree(sftr_pl);
__mlxsw_sp_port_flood_table_set(mlxsw_sp_port, idx_begin, idx_end,
MLXSW_SP_FLOOD_TABLE_UC, !uc_set);
return err;
}
static int mlxsw_sp_port_uc_flood_set(struct mlxsw_sp_port *mlxsw_sp_port,
bool set)
static int mlxsw_sp_port_flood_table_set(struct mlxsw_sp_port *mlxsw_sp_port,
enum mlxsw_sp_flood_table table,
bool set)
{
struct net_device *dev = mlxsw_sp_port->dev;
u16 vid, last_visited_vid;
......@@ -220,13 +251,13 @@ static int mlxsw_sp_port_uc_flood_set(struct mlxsw_sp_port *mlxsw_sp_port,
u16 fid = mlxsw_sp_vport_fid_get(mlxsw_sp_port)->fid;
u16 vfid = mlxsw_sp_fid_to_vfid(fid);
return __mlxsw_sp_port_flood_set(mlxsw_sp_port, vfid, vfid,
set, true);
return __mlxsw_sp_port_flood_table_set(mlxsw_sp_port, vfid,
vfid, table, set);
}
for_each_set_bit(vid, mlxsw_sp_port->active_vlans, VLAN_N_VID) {
err = __mlxsw_sp_port_flood_set(mlxsw_sp_port, vid, vid, set,
true);
err = __mlxsw_sp_port_flood_table_set(mlxsw_sp_port, vid, vid,
table, set);
if (err) {
last_visited_vid = vid;
goto err_port_flood_set;
......@@ -237,21 +268,53 @@ static int mlxsw_sp_port_uc_flood_set(struct mlxsw_sp_port *mlxsw_sp_port,
err_port_flood_set:
for_each_set_bit(vid, mlxsw_sp_port->active_vlans, last_visited_vid)
__mlxsw_sp_port_flood_set(mlxsw_sp_port, vid, vid, !set, true);
__mlxsw_sp_port_flood_table_set(mlxsw_sp_port, vid, vid, table,
!set);
netdev_err(dev, "Failed to configure unicast flooding\n");
return err;
}
static int mlxsw_sp_port_mc_disabled_set(struct mlxsw_sp_port *mlxsw_sp_port,
struct switchdev_trans *trans,
bool mc_disabled)
{
int set;
int err = 0;
if (switchdev_trans_ph_prepare(trans))
return 0;
if (mlxsw_sp_port->mc_router != mlxsw_sp_port->mc_flood) {
set = mc_disabled ?
mlxsw_sp_port->mc_flood : mlxsw_sp_port->mc_router;
err = mlxsw_sp_port_flood_table_set(mlxsw_sp_port,
MLXSW_SP_FLOOD_TABLE_MC,
set);
}
if (!err)
mlxsw_sp_port->mc_disabled = mc_disabled;
return err;
}
int mlxsw_sp_vport_flood_set(struct mlxsw_sp_port *mlxsw_sp_vport, u16 fid,
bool set)
{
bool mc_set = set;
u16 vfid;
/* In case of vFIDs, index into the flooding table is relative to
* the start of the vFIDs range.
*/
vfid = mlxsw_sp_fid_to_vfid(fid);
return __mlxsw_sp_port_flood_set(mlxsw_sp_vport, vfid, vfid, set, set);
if (set)
mc_set = mlxsw_sp_vport->mc_disabled ?
mlxsw_sp_vport->mc_flood : mlxsw_sp_vport->mc_router;
return __mlxsw_sp_port_flood_set(mlxsw_sp_vport, vfid, vfid, set, set,
mc_set);
}
static int mlxsw_sp_port_learning_set(struct mlxsw_sp_port *mlxsw_sp_port,
......@@ -297,8 +360,9 @@ static int mlxsw_sp_port_attr_br_flags_set(struct mlxsw_sp_port *mlxsw_sp_port,
return 0;
if ((uc_flood ^ brport_flags) & BR_FLOOD) {
err = mlxsw_sp_port_uc_flood_set(mlxsw_sp_port,
!mlxsw_sp_port->uc_flood);
err = mlxsw_sp_port_flood_table_set(mlxsw_sp_port,
MLXSW_SP_FLOOD_TABLE_UC,
!mlxsw_sp_port->uc_flood);
if (err)
return err;
}
......@@ -318,8 +382,9 @@ static int mlxsw_sp_port_attr_br_flags_set(struct mlxsw_sp_port *mlxsw_sp_port,
err_port_learning_set:
if ((uc_flood ^ brport_flags) & BR_FLOOD)
mlxsw_sp_port_uc_flood_set(mlxsw_sp_port,
mlxsw_sp_port->uc_flood);
mlxsw_sp_port_flood_table_set(mlxsw_sp_port,
MLXSW_SP_FLOOD_TABLE_UC,
mlxsw_sp_port->uc_flood);
return err;
}
......@@ -371,6 +436,22 @@ static int mlxsw_sp_port_attr_br_vlan_set(struct mlxsw_sp_port *mlxsw_sp_port,
return 0;
}
static int mlxsw_sp_port_attr_mc_router_set(struct mlxsw_sp_port *mlxsw_sp_port,
struct switchdev_trans *trans,
bool is_port_mc_router)
{
if (switchdev_trans_ph_prepare(trans))
return 0;
mlxsw_sp_port->mc_router = is_port_mc_router;
if (!mlxsw_sp_port->mc_disabled)
return mlxsw_sp_port_flood_table_set(mlxsw_sp_port,
MLXSW_SP_FLOOD_TABLE_MC,
is_port_mc_router);
return 0;
}
static int mlxsw_sp_port_attr_set(struct net_device *dev,
const struct switchdev_attr *attr,
struct switchdev_trans *trans)
......@@ -400,6 +481,14 @@ static int mlxsw_sp_port_attr_set(struct net_device *dev,
attr->orig_dev,
attr->u.vlan_filtering);
break;
case SWITCHDEV_ATTR_ID_PORT_MROUTER:
err = mlxsw_sp_port_attr_mc_router_set(mlxsw_sp_port, trans,
attr->u.mrouter);
break;
case SWITCHDEV_ATTR_ID_BRIDGE_MC_DISABLED:
err = mlxsw_sp_port_mc_disabled_set(mlxsw_sp_port, trans,
attr->u.mc_disabled);
break;
default:
err = -EOPNOTSUPP;
break;
......@@ -545,6 +634,7 @@ static int mlxsw_sp_port_fid_map(struct mlxsw_sp_port *mlxsw_sp_port, u16 fid,
static int mlxsw_sp_port_fid_join(struct mlxsw_sp_port *mlxsw_sp_port,
u16 fid_begin, u16 fid_end)
{
bool mc_flood;
int fid, err;
for (fid = fid_begin; fid <= fid_end; fid++) {
......@@ -553,8 +643,12 @@ static int mlxsw_sp_port_fid_join(struct mlxsw_sp_port *mlxsw_sp_port,
goto err_port_fid_join;
}
mc_flood = mlxsw_sp_port->mc_disabled ?
mlxsw_sp_port->mc_flood : mlxsw_sp_port->mc_router;
err = __mlxsw_sp_port_flood_set(mlxsw_sp_port, fid_begin, fid_end,
mlxsw_sp_port->uc_flood, true);
mlxsw_sp_port->uc_flood, true,
mc_flood);
if (err)
goto err_port_flood_set;
......@@ -570,7 +664,7 @@ static int mlxsw_sp_port_fid_join(struct mlxsw_sp_port *mlxsw_sp_port,
for (fid--; fid >= fid_begin; fid--)
mlxsw_sp_port_fid_map(mlxsw_sp_port, fid, false);
__mlxsw_sp_port_flood_set(mlxsw_sp_port, fid_begin, fid_end, false,
false);
false, false);
err_port_flood_set:
fid = fid_end;
err_port_fid_join:
......@@ -588,7 +682,7 @@ static void mlxsw_sp_port_fid_leave(struct mlxsw_sp_port *mlxsw_sp_port,
mlxsw_sp_port_fid_map(mlxsw_sp_port, fid, false);
__mlxsw_sp_port_flood_set(mlxsw_sp_port, fid_begin, fid_end, false,
false);
false, false);
for (fid = fid_begin; fid <= fid_end; fid++)
__mlxsw_sp_port_fid_leave(mlxsw_sp_port, fid);
......
......@@ -46,8 +46,10 @@ enum switchdev_attr_id {
SWITCHDEV_ATTR_ID_PORT_PARENT_ID,
SWITCHDEV_ATTR_ID_PORT_STP_STATE,
SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS,
SWITCHDEV_ATTR_ID_PORT_MROUTER,
SWITCHDEV_ATTR_ID_BRIDGE_AGEING_TIME,
SWITCHDEV_ATTR_ID_BRIDGE_VLAN_FILTERING,
SWITCHDEV_ATTR_ID_BRIDGE_MC_DISABLED,
};
struct switchdev_attr {
......@@ -60,8 +62,10 @@ struct switchdev_attr {
struct netdev_phys_item_id ppid; /* PORT_PARENT_ID */
u8 stp_state; /* PORT_STP_STATE */
unsigned long brport_flags; /* PORT_BRIDGE_FLAGS */
bool mrouter; /* PORT_MROUTER */
clock_t ageing_time; /* BRIDGE_AGEING_TIME */
bool vlan_filtering; /* BRIDGE_VLAN_FILTERING */
bool mc_disabled; /* MC_DISABLED */
} u;
};
......
......@@ -27,6 +27,7 @@
#include <linux/inetdevice.h>
#include <linux/mroute.h>
#include <net/ip.h>
#include <net/switchdev.h>
#if IS_ENABLED(CONFIG_IPV6)
#include <net/ipv6.h>
#include <net/mld.h>
......@@ -46,6 +47,7 @@ static void br_ip4_multicast_leave_group(struct net_bridge *br,
__u16 vid,
const unsigned char *src);
static void __del_port_router(struct net_bridge_port *p);
#if IS_ENABLED(CONFIG_IPV6)
static void br_ip6_multicast_leave_group(struct net_bridge *br,
struct net_bridge_port *port,
......@@ -849,16 +851,10 @@ static void br_multicast_router_expired(unsigned long data)
spin_lock(&br->multicast_lock);
if (port->multicast_router == MDB_RTR_TYPE_DISABLED ||
port->multicast_router == MDB_RTR_TYPE_PERM ||
timer_pending(&port->multicast_router_timer) ||
hlist_unhashed(&port->rlist))
timer_pending(&port->multicast_router_timer))
goto out;
hlist_del_init_rcu(&port->rlist);
br_rtr_notify(br->dev, port, RTM_DELMDB);
/* Don't allow timer refresh if the router expired */
if (port->multicast_router == MDB_RTR_TYPE_TEMP)
port->multicast_router = MDB_RTR_TYPE_TEMP_QUERY;
__del_port_router(port);
out:
spin_unlock(&br->multicast_lock);
}
......@@ -1007,6 +1003,18 @@ static void br_ip6_multicast_port_query_expired(unsigned long data)
}
#endif
static void br_mc_disabled_update(struct net_device *dev, bool value)
{
struct switchdev_attr attr = {
.orig_dev = dev,
.id = SWITCHDEV_ATTR_ID_BRIDGE_MC_DISABLED,
.flags = SWITCHDEV_F_DEFER,
.u.mc_disabled = value,
};
switchdev_port_attr_set(dev, &attr);
}
int br_multicast_add_port(struct net_bridge_port *port)
{
port->multicast_router = MDB_RTR_TYPE_TEMP_QUERY;
......@@ -1019,6 +1027,8 @@ int br_multicast_add_port(struct net_bridge_port *port)
setup_timer(&port->ip6_own_query.timer,
br_ip6_multicast_port_query_expired, (unsigned long)port);
#endif
br_mc_disabled_update(port->dev, port->br->multicast_disabled);
port->mcast_stats = netdev_alloc_pcpu_stats(struct bridge_mcast_stats);
if (!port->mcast_stats)
return -ENOMEM;
......@@ -1086,13 +1096,8 @@ void br_multicast_disable_port(struct net_bridge_port *port)
if (!(pg->flags & MDB_PG_FLAGS_PERMANENT))
br_multicast_del_pg(br, pg);
if (!hlist_unhashed(&port->rlist)) {
hlist_del_init_rcu(&port->rlist);
br_rtr_notify(br->dev, port, RTM_DELMDB);
/* Don't allow timer refresh if disabling */
if (port->multicast_router == MDB_RTR_TYPE_TEMP)
port->multicast_router = MDB_RTR_TYPE_TEMP_QUERY;
}
__del_port_router(port);
del_timer(&port->multicast_router_timer);
del_timer(&port->ip4_own_query.timer);
#if IS_ENABLED(CONFIG_IPV6)
......@@ -1312,6 +1317,19 @@ br_multicast_update_query_timer(struct net_bridge *br,
mod_timer(&query->timer, jiffies + br->multicast_querier_interval);
}
static void br_port_mc_router_state_change(struct net_bridge_port *p,
bool is_mc_router)
{
struct switchdev_attr attr = {
.orig_dev = p->dev,
.id = SWITCHDEV_ATTR_ID_PORT_MROUTER,
.flags = SWITCHDEV_F_DEFER,
.u.mrouter = is_mc_router,
};
switchdev_port_attr_set(p->dev, &attr);
}
/*
* Add port to router_list
* list is maintained ordered by pointer value
......@@ -1337,6 +1355,7 @@ static void br_multicast_add_router(struct net_bridge *br,
else
hlist_add_head_rcu(&port->rlist, &br->router_list);
br_rtr_notify(br->dev, port, RTM_NEWMDB);
br_port_mc_router_state_change(port, true);
}
static void br_multicast_mark_router(struct net_bridge *br,
......@@ -2044,6 +2063,11 @@ static void __del_port_router(struct net_bridge_port *p)
return;
hlist_del_init_rcu(&p->rlist);
br_rtr_notify(p->br->dev, p, RTM_DELMDB);
br_port_mc_router_state_change(p, false);
/* don't allow timer refresh */
if (p->multicast_router == MDB_RTR_TYPE_TEMP)
p->multicast_router = MDB_RTR_TYPE_TEMP_QUERY;
}
int br_multicast_set_port_router(struct net_bridge_port *p, unsigned long val)
......@@ -2121,6 +2145,7 @@ int br_multicast_toggle(struct net_bridge *br, unsigned long val)
if (br->multicast_disabled == !val)
goto unlock;
br_mc_disabled_update(br->dev, !val);
br->multicast_disabled = !val;
if (br->multicast_disabled)
goto unlock;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment