Commit 74918945 authored by Vladimir Oltean's avatar Vladimir Oltean Committed by David S. Miller

net: dsa: replay a deletion of switchdev objects for ports leaving a bridged LAG

When a DSA switch port leaves a bonding interface that is under a
bridge, there might be dangling switchdev objects on that port left
behind, because the bridge is not aware that its lower interface (the
bond) changed state in any way.

Call the bridge replay helpers with adding=false before changing
dp->bridge_dev to NULL, because we need to simulate to
dsa_slave_port_obj_del() that these notifications were emitted by the
bridge.

We add this hook to the NETDEV_PRECHANGEUPPER event handler, because
we are calling into switchdev (and the __switchdev_handle_port_obj_del
fanout helpers expect the upper/lower adjacency lists to still be valid)
and PRECHANGEUPPER is the last moment in time when they still are.
Signed-off-by: default avatarVladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 4ede74e7
......@@ -188,12 +188,16 @@ void dsa_port_disable_rt(struct dsa_port *dp);
void dsa_port_disable(struct dsa_port *dp);
int dsa_port_bridge_join(struct dsa_port *dp, struct net_device *br,
struct netlink_ext_ack *extack);
int dsa_port_pre_bridge_leave(struct dsa_port *dp, struct net_device *br,
struct netlink_ext_ack *extack);
void dsa_port_bridge_leave(struct dsa_port *dp, struct net_device *br);
int dsa_port_lag_change(struct dsa_port *dp,
struct netdev_lag_lower_state_info *linfo);
int dsa_port_lag_join(struct dsa_port *dp, struct net_device *lag_dev,
struct netdev_lag_upper_info *uinfo,
struct netlink_ext_ack *extack);
int dsa_port_pre_lag_leave(struct dsa_port *dp, struct net_device *lag_dev,
struct netlink_ext_ack *extack);
void dsa_port_lag_leave(struct dsa_port *dp, struct net_device *lag_dev);
int dsa_port_vlan_filtering(struct dsa_port *dp, bool vlan_filtering,
struct netlink_ext_ack *extack);
......
......@@ -212,7 +212,33 @@ static int dsa_port_switchdev_sync(struct dsa_port *dp,
return 0;
}
static void dsa_port_switchdev_unsync(struct dsa_port *dp)
static int dsa_port_switchdev_unsync_objs(struct dsa_port *dp,
struct net_device *br,
struct netlink_ext_ack *extack)
{
struct net_device *brport_dev = dsa_port_to_bridge_port(dp);
int err;
/* Delete the switchdev objects left on this port */
err = br_mdb_replay(br, brport_dev, dp, false,
&dsa_slave_switchdev_blocking_notifier, extack);
if (err && err != -EOPNOTSUPP)
return err;
err = br_fdb_replay(br, brport_dev, dp, false,
&dsa_slave_switchdev_notifier);
if (err && err != -EOPNOTSUPP)
return err;
err = br_vlan_replay(br, brport_dev, dp, false,
&dsa_slave_switchdev_blocking_notifier, extack);
if (err && err != -EOPNOTSUPP)
return err;
return 0;
}
static void dsa_port_switchdev_unsync_attrs(struct dsa_port *dp)
{
/* Configure the port for standalone mode (no address learning,
* flood everything).
......@@ -278,6 +304,12 @@ int dsa_port_bridge_join(struct dsa_port *dp, struct net_device *br,
return err;
}
int dsa_port_pre_bridge_leave(struct dsa_port *dp, struct net_device *br,
struct netlink_ext_ack *extack)
{
return dsa_port_switchdev_unsync_objs(dp, br, extack);
}
void dsa_port_bridge_leave(struct dsa_port *dp, struct net_device *br)
{
struct dsa_notifier_bridge_info info = {
......@@ -297,7 +329,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");
dsa_port_switchdev_unsync(dp);
dsa_port_switchdev_unsync_attrs(dp);
}
int dsa_port_lag_change(struct dsa_port *dp,
......@@ -365,6 +397,15 @@ int dsa_port_lag_join(struct dsa_port *dp, struct net_device *lag,
return err;
}
int dsa_port_pre_lag_leave(struct dsa_port *dp, struct net_device *lag,
struct netlink_ext_ack *extack)
{
if (dp->bridge_dev)
return dsa_port_pre_bridge_leave(dp, dp->bridge_dev, extack);
return 0;
}
void dsa_port_lag_leave(struct dsa_port *dp, struct net_device *lag)
{
struct dsa_notifier_lag_info info = {
......
......@@ -2077,6 +2077,26 @@ static int dsa_slave_changeupper(struct net_device *dev,
return err;
}
static int dsa_slave_prechangeupper(struct net_device *dev,
struct netdev_notifier_changeupper_info *info)
{
struct dsa_port *dp = dsa_slave_to_port(dev);
struct netlink_ext_ack *extack;
int err = 0;
extack = netdev_notifier_info_to_extack(&info->info);
if (netif_is_bridge_master(info->upper_dev) && !info->linking)
err = dsa_port_pre_bridge_leave(dp, info->upper_dev, extack);
else if (netif_is_lag_master(info->upper_dev) && !info->linking)
err = dsa_port_pre_lag_leave(dp, info->upper_dev, extack);
/* dsa_port_pre_hsr_leave is not yet necessary since hsr cannot be
* meaningfully enslaved to a bridge yet
*/
return notifier_from_errno(err);
}
static int
dsa_slave_lag_changeupper(struct net_device *dev,
struct netdev_notifier_changeupper_info *info)
......@@ -2103,6 +2123,35 @@ dsa_slave_lag_changeupper(struct net_device *dev,
return err;
}
/* Same as dsa_slave_lag_changeupper() except that it calls
* dsa_slave_prechangeupper()
*/
static int
dsa_slave_lag_prechangeupper(struct net_device *dev,
struct netdev_notifier_changeupper_info *info)
{
struct net_device *lower;
struct list_head *iter;
int err = NOTIFY_DONE;
struct dsa_port *dp;
netdev_for_each_lower_dev(dev, lower, iter) {
if (!dsa_slave_dev_check(lower))
continue;
dp = dsa_slave_to_port(lower);
if (!dp->lag_dev)
/* Software LAG */
continue;
err = dsa_slave_prechangeupper(lower, info);
if (notifier_to_errno(err))
break;
}
return err;
}
static int
dsa_prevent_bridging_8021q_upper(struct net_device *dev,
struct netdev_notifier_changeupper_info *info)
......@@ -2206,6 +2255,12 @@ static int dsa_slave_netdevice_event(struct notifier_block *nb,
if (err != NOTIFY_DONE)
return err;
if (dsa_slave_dev_check(dev))
return dsa_slave_prechangeupper(dev, ptr);
if (netif_is_lag_master(dev))
return dsa_slave_lag_prechangeupper(dev, ptr);
break;
}
case NETDEV_CHANGEUPPER:
......
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