@@ -205,6 +205,7 @@ struct dsa_port {
bool mc_flood;
/* Knobs from bridge */
unsigned long br_flags;
+ bool mc_disabled;
bool mrouter;
struct list_head list;
@@ -564,6 +565,8 @@ struct dsa_switch_ops {
const struct switchdev_obj_port_mdb *mdb);
int (*port_mdb_del)(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_mdb *mdb);
+ int (*port_igmp_mld_snoop)(struct dsa_switch *ds, int port,
+ bool enable);
/*
* RXNFC
*/
@@ -24,6 +24,7 @@ enum {
DSA_NOTIFIER_VLAN_ADD,
DSA_NOTIFIER_VLAN_DEL,
DSA_NOTIFIER_MTU,
+ DSA_NOTIFIER_MC_DISABLED,
};
/* DSA_NOTIFIER_AGEING_TIME */
@@ -72,6 +73,14 @@ struct dsa_notifier_mtu_info {
int mtu;
};
+/* DSA_NOTIFIER_MC_DISABLED */
+struct dsa_notifier_mc_disabled_info {
+ int tree_index;
+ int sw_index;
+ struct net_device *br;
+ bool mc_disabled;
+};
+
struct dsa_switchdev_event_work {
struct dsa_switch *ds;
int port;
@@ -150,6 +159,10 @@ int dsa_port_bridge_join(struct dsa_port *dp, struct net_device *br);
void dsa_port_bridge_leave(struct dsa_port *dp, struct net_device *br);
int dsa_port_vlan_filtering(struct dsa_port *dp, bool vlan_filtering,
struct switchdev_trans *trans);
+int dsa_port_multicast_toggle(struct dsa_switch *ds, int port,
+ bool mc_disabled);
+int dsa_port_mc_disabled(struct dsa_port *dp, bool mc_disabled,
+ struct switchdev_trans *trans);
bool dsa_port_skip_vlan_configuration(struct dsa_port *dp);
int dsa_port_ageing_time(struct dsa_port *dp, clock_t ageing_clock,
struct switchdev_trans *trans);
@@ -144,6 +144,7 @@ int dsa_port_bridge_join(struct dsa_port *dp, struct net_device *br)
};
int err;
+ dp->cpu_dp->mc_disabled = !br_multicast_enabled(br);
dp->cpu_dp->mrouter = br_multicast_router(br);
/* Here the interface is already bridged. Reflect the current
@@ -175,6 +176,7 @@ void dsa_port_bridge_leave(struct dsa_port *dp, struct net_device *br)
if (err)
pr_err("DSA: failed to notify DSA_NOTIFIER_BRIDGE_LEAVE\n");
+ dp->cpu_dp->mc_disabled = true;
dp->cpu_dp->mrouter = false;
/* Port is leaving the bridge, disable host flooding and enable
@@ -299,7 +301,17 @@ static int dsa_port_update_flooding(struct dsa_port *dp, int uc_flood_count,
return 0;
uc_flood = !!uc_flood_count;
- mc_flood = dp->mrouter;
+ /* As explained in commit 8ecd4591e761 ("mlxsw: spectrum: Add an option
+ * to flood mc by mc_router_port"), the decision whether to flood a
+ * multicast packet to a port depends on 3 flags: mc_disabled,
+ * mc_router_port, mc_flood.
+ * If mc_disabled is on, the port will be flooded according to
+ * mc_flood, otherwise, according to mc_router_port.
+ */
+ if (dp->mc_disabled)
+ mc_flood = !!mc_flood_count;
+ else
+ mc_flood = dp->mrouter;
uc_flood_changed = dp->uc_flood ^ uc_flood;
mc_flood_changed = dp->mc_flood ^ mc_flood;
@@ -388,6 +400,39 @@ int dsa_port_mrouter(struct dsa_port *dp, bool mrouter,
dp->mc_flood_count);
}
+int dsa_port_multicast_toggle(struct dsa_switch *ds, int port, bool mc_disabled)
+{
+ struct dsa_port *dp = dsa_to_port(ds, port);
+ int err;
+
+ if (ds->ops->port_igmp_mld_snoop) {
+ err = ds->ops->port_igmp_mld_snoop(ds, port, !mc_disabled);
+ if (err)
+ return err;
+ }
+
+ dp->mc_disabled = mc_disabled;
+
+ return dsa_port_update_flooding(dp, dp->uc_flood_count,
+ dp->mc_flood_count);
+}
+
+int dsa_port_mc_disabled(struct dsa_port *dp, bool mc_disabled,
+ struct switchdev_trans *trans)
+{
+ struct dsa_notifier_mc_disabled_info info = {
+ .tree_index = dp->ds->dst->index,
+ .sw_index = dp->ds->index,
+ .br = dp->bridge_dev,
+ .mc_disabled = mc_disabled,
+ };
+
+ if (switchdev_trans_ph_prepare(trans))
+ return 0;
+
+ return dsa_broadcast(DSA_NOTIFIER_MC_DISABLED, &info);
+}
+
int dsa_port_mtu_change(struct dsa_port *dp, int new_mtu,
bool propagate_upstream)
{
@@ -475,6 +475,9 @@ static int dsa_slave_port_attr_set(struct net_device *dev,
/* The local bridge is a multicast router */
ret = dsa_port_mrouter(dp->cpu_dp, attr->u.mrouter, trans);
break;
+ case SWITCHDEV_ATTR_ID_BRIDGE_MC_DISABLED:
+ ret = dsa_port_mc_disabled(dp, attr->u.mc_disabled, trans);
+ break;
default:
ret = -EOPNOTSUPP;
break;
@@ -337,6 +337,39 @@ static int dsa_switch_vlan_del(struct dsa_switch *ds,
return 0;
}
+static bool
+dsa_switch_mc_disabled_match(struct dsa_switch *ds, int port,
+ struct dsa_notifier_mc_disabled_info *info)
+{
+ struct dsa_port *dp = dsa_to_port(ds, port);
+ struct dsa_switch_tree *dst = ds->dst;
+
+ if (dp->bridge_dev == info->br)
+ return true;
+
+ if (dst->index == info->tree_index && ds->index == info->sw_index)
+ return dsa_is_cpu_port(ds, port) || dsa_is_dsa_port(ds, port);
+
+ return false;
+}
+
+static int dsa_switch_mc_disabled(struct dsa_switch *ds,
+ struct dsa_notifier_mc_disabled_info *info)
+{
+ bool mc_disabled = info->mc_disabled;
+ int port, err;
+
+ for (port = 0; port < ds->num_ports; port++) {
+ if (dsa_switch_mc_disabled_match(ds, port, info)) {
+ err = dsa_port_multicast_toggle(ds, port, mc_disabled);
+ if (err)
+ return err;
+ }
+ }
+
+ return 0;
+}
+
static int dsa_switch_event(struct notifier_block *nb,
unsigned long event, void *info)
{
@@ -374,6 +407,9 @@ static int dsa_switch_event(struct notifier_block *nb,
case DSA_NOTIFIER_MTU:
err = dsa_switch_mtu(ds, info);
break;
+ case DSA_NOTIFIER_MC_DISABLED:
+ err = dsa_switch_mc_disabled(ds, info);
+ break;
default:
err = -EOPNOTSUPP;
break;