Commit f796fcd6 authored by David S. Miller's avatar David S. Miller

Merge branch 'bridge-port-offload'

Vladimir Oltean says:

====================
Let switchdev drivers offload and unoffload bridge ports at their own convenience

This series introduces an explicit API through which switchdev drivers
mark a bridge port as offloaded or not:
- switchdev_bridge_port_offload()
- switchdev_bridge_port_unoffload()

Currently, the bridge assumes that a port is offloaded if
dev_get_port_parent_id(dev, &ppid, recurse=true) returns something, but
that is just an assumption that breaks some use cases (like a
non-offloaded LAG interface on top of a switchdev port, bridged with
other switchdev ports).

Along with some consolidation of the bridge logic to assign a "switchdev
offloading mark" to a port (now better called a "hardware domain"), this
series allows the bridge driver side to no longer impose restrictions on
that configuration.

Right now, all switchdev drivers must be modified to use the explicit
API, but more and more logic can then be placed centrally in the bridge
and therefore ease the job of a switchdev driver writer in the future.

For example, the first thing we can hook into the explicit switchdev
offloading API calls are the switchdev object and FDB replay helpers.
So far, these have only been used by DSA in "pull" mode (where the
driver must ask for them). Adding the replay helpers to other drivers
involves a lot of repetition. But by moving the helpers inside the
bridge port offload/unoffload hook points, we can move the entire replay
process to "push" mode (where the bridge provides them automatically).

The explicit switchdev offloading API will see further extensions in the
future.

The patches were split from a larger series for easier review:
https://patchwork.kernel.org/project/netdevbpf/cover/20210718214434.3938850-1-vladimir.oltean@nxp.com/

Changes in v6:
- Make the switchdev replay helpers opt-in
- Opt out of the replay helpers for mlxsw, rocker, prestera, sparx5,
  cpsw, am65-cpsw
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents c2255ff4 4e51bf44
......@@ -1889,8 +1889,12 @@ static int dpaa2_switch_port_attr_set_event(struct net_device *netdev,
return notifier_from_errno(err);
}
static struct notifier_block dpaa2_switch_port_switchdev_nb;
static struct notifier_block dpaa2_switch_port_switchdev_blocking_nb;
static int dpaa2_switch_port_bridge_join(struct net_device *netdev,
struct net_device *upper_dev)
struct net_device *upper_dev,
struct netlink_ext_ack *extack)
{
struct ethsw_port_priv *port_priv = netdev_priv(netdev);
struct ethsw_core *ethsw = port_priv->ethsw_data;
......@@ -1906,8 +1910,8 @@ static int dpaa2_switch_port_bridge_join(struct net_device *netdev,
other_port_priv = netdev_priv(other_dev);
if (other_port_priv->ethsw_data != port_priv->ethsw_data) {
netdev_err(netdev,
"Interface from a different DPSW is in the bridge already!\n");
NL_SET_ERR_MSG_MOD(extack,
"Interface from a different DPSW is in the bridge already");
return -EINVAL;
}
}
......@@ -1929,8 +1933,16 @@ static int dpaa2_switch_port_bridge_join(struct net_device *netdev,
if (err)
goto err_egress_flood;
err = switchdev_bridge_port_offload(netdev, netdev, NULL,
&dpaa2_switch_port_switchdev_nb,
&dpaa2_switch_port_switchdev_blocking_nb,
extack);
if (err)
goto err_switchdev_offload;
return 0;
err_switchdev_offload:
err_egress_flood:
dpaa2_switch_port_set_fdb(port_priv, NULL);
return err;
......@@ -1956,6 +1968,13 @@ static int dpaa2_switch_port_restore_rxvlan(struct net_device *vdev, int vid, vo
return dpaa2_switch_port_vlan_add(arg, vlan_proto, vid);
}
static void dpaa2_switch_port_pre_bridge_leave(struct net_device *netdev)
{
switchdev_bridge_port_unoffload(netdev, NULL,
&dpaa2_switch_port_switchdev_nb,
&dpaa2_switch_port_switchdev_blocking_nb);
}
static int dpaa2_switch_port_bridge_leave(struct net_device *netdev)
{
struct ethsw_port_priv *port_priv = netdev_priv(netdev);
......@@ -2029,6 +2048,28 @@ static int dpaa2_switch_prevent_bridging_with_8021q_upper(struct net_device *net
return 0;
}
static int
dpaa2_switch_prechangeupper_sanity_checks(struct net_device *netdev,
struct net_device *upper_dev,
struct netlink_ext_ack *extack)
{
int err;
if (!br_vlan_enabled(upper_dev)) {
NL_SET_ERR_MSG_MOD(extack, "Cannot join a VLAN-unaware bridge");
return -EOPNOTSUPP;
}
err = dpaa2_switch_prevent_bridging_with_8021q_upper(netdev);
if (err) {
NL_SET_ERR_MSG_MOD(extack,
"Cannot join a bridge while VLAN uppers are present");
return 0;
}
return 0;
}
static int dpaa2_switch_port_netdevice_event(struct notifier_block *nb,
unsigned long event, void *ptr)
{
......@@ -2049,25 +2090,23 @@ static int dpaa2_switch_port_netdevice_event(struct notifier_block *nb,
if (!netif_is_bridge_master(upper_dev))
break;
if (!br_vlan_enabled(upper_dev)) {
NL_SET_ERR_MSG_MOD(extack, "Cannot join a VLAN-unaware bridge");
err = -EOPNOTSUPP;
err = dpaa2_switch_prechangeupper_sanity_checks(netdev,
upper_dev,
extack);
if (err)
goto out;
}
err = dpaa2_switch_prevent_bridging_with_8021q_upper(netdev);
if (err) {
NL_SET_ERR_MSG_MOD(extack,
"Cannot join a bridge while VLAN uppers are present");
goto out;
}
if (!info->linking)
dpaa2_switch_port_pre_bridge_leave(netdev);
break;
case NETDEV_CHANGEUPPER:
upper_dev = info->upper_dev;
if (netif_is_bridge_master(upper_dev)) {
if (info->linking)
err = dpaa2_switch_port_bridge_join(netdev, upper_dev);
err = dpaa2_switch_port_bridge_join(netdev,
upper_dev,
extack);
else
err = dpaa2_switch_port_bridge_leave(netdev);
}
......
......@@ -746,7 +746,8 @@ static int prestera_netdev_port_event(struct net_device *lower,
case NETDEV_CHANGEUPPER:
if (netif_is_bridge_master(upper)) {
if (info->linking)
return prestera_bridge_port_join(upper, port);
return prestera_bridge_port_join(upper, port,
extack);
else
prestera_bridge_port_leave(upper, port);
} else if (netif_is_lag_master(upper)) {
......
......@@ -480,7 +480,8 @@ prestera_bridge_1d_port_join(struct prestera_bridge_port *br_port)
}
int prestera_bridge_port_join(struct net_device *br_dev,
struct prestera_port *port)
struct prestera_port *port,
struct netlink_ext_ack *extack)
{
struct prestera_switchdev *swdev = port->sw->swdev;
struct prestera_bridge_port *br_port;
......@@ -500,6 +501,11 @@ int prestera_bridge_port_join(struct net_device *br_dev,
goto err_brport_create;
}
err = switchdev_bridge_port_offload(br_port->dev, port->dev, NULL,
NULL, NULL, extack);
if (err)
goto err_switchdev_offload;
if (bridge->vlan_enabled)
return 0;
......@@ -510,6 +516,8 @@ int prestera_bridge_port_join(struct net_device *br_dev,
return 0;
err_port_join:
switchdev_bridge_port_unoffload(br_port->dev, NULL, NULL, NULL);
err_switchdev_offload:
prestera_bridge_port_put(br_port);
err_brport_create:
prestera_bridge_put(bridge);
......@@ -584,6 +592,8 @@ void prestera_bridge_port_leave(struct net_device *br_dev,
else
prestera_bridge_1d_port_leave(br_port);
switchdev_bridge_port_unoffload(br_port->dev, NULL, NULL, NULL);
prestera_hw_port_learning_set(port, false);
prestera_hw_port_flood_set(port, BR_FLOOD | BR_MCAST_FLOOD, 0);
prestera_port_vid_stp_set(port, PRESTERA_VID_ALL, BR_STATE_FORWARDING);
......
......@@ -8,7 +8,8 @@ int prestera_switchdev_init(struct prestera_switch *sw);
void prestera_switchdev_fini(struct prestera_switch *sw);
int prestera_bridge_port_join(struct net_device *br_dev,
struct prestera_port *port);
struct prestera_port *port,
struct netlink_ext_ack *extack);
void prestera_bridge_port_leave(struct net_device *br_dev,
struct prestera_port *port);
......
......@@ -335,14 +335,16 @@ mlxsw_sp_bridge_port_find(struct mlxsw_sp_bridge *bridge,
static struct mlxsw_sp_bridge_port *
mlxsw_sp_bridge_port_create(struct mlxsw_sp_bridge_device *bridge_device,
struct net_device *brport_dev)
struct net_device *brport_dev,
struct netlink_ext_ack *extack)
{
struct mlxsw_sp_bridge_port *bridge_port;
struct mlxsw_sp_port *mlxsw_sp_port;
int err;
bridge_port = kzalloc(sizeof(*bridge_port), GFP_KERNEL);
if (!bridge_port)
return NULL;
return ERR_PTR(-ENOMEM);
mlxsw_sp_port = mlxsw_sp_port_dev_lower_find(brport_dev);
bridge_port->lagged = mlxsw_sp_port->lagged;
......@@ -359,12 +361,23 @@ mlxsw_sp_bridge_port_create(struct mlxsw_sp_bridge_device *bridge_device,
list_add(&bridge_port->list, &bridge_device->ports_list);
bridge_port->ref_count = 1;
err = switchdev_bridge_port_offload(brport_dev, mlxsw_sp_port->dev,
NULL, NULL, NULL, extack);
if (err)
goto err_switchdev_offload;
return bridge_port;
err_switchdev_offload:
list_del(&bridge_port->list);
kfree(bridge_port);
return ERR_PTR(err);
}
static void
mlxsw_sp_bridge_port_destroy(struct mlxsw_sp_bridge_port *bridge_port)
{
switchdev_bridge_port_unoffload(bridge_port->dev, NULL, NULL, NULL);
list_del(&bridge_port->list);
WARN_ON(!list_empty(&bridge_port->vlans_list));
kfree(bridge_port);
......@@ -390,9 +403,10 @@ mlxsw_sp_bridge_port_get(struct mlxsw_sp_bridge *bridge,
if (IS_ERR(bridge_device))
return ERR_CAST(bridge_device);
bridge_port = mlxsw_sp_bridge_port_create(bridge_device, brport_dev);
if (!bridge_port) {
err = -ENOMEM;
bridge_port = mlxsw_sp_bridge_port_create(bridge_device, brport_dev,
extack);
if (IS_ERR(bridge_port)) {
err = PTR_ERR(bridge_port);
goto err_bridge_port_create;
}
......
......@@ -93,9 +93,12 @@ static int sparx5_port_attr_set(struct net_device *dev, const void *ctx,
}
static int sparx5_port_bridge_join(struct sparx5_port *port,
struct net_device *bridge)
struct net_device *bridge,
struct netlink_ext_ack *extack)
{
struct sparx5 *sparx5 = port->sparx5;
struct net_device *ndev = port->ndev;
int err;
if (bitmap_empty(sparx5->bridge_mask, SPX5_PORTS))
/* First bridged port */
......@@ -109,12 +112,21 @@ static int sparx5_port_bridge_join(struct sparx5_port *port,
set_bit(port->portno, sparx5->bridge_mask);
err = switchdev_bridge_port_offload(ndev, ndev, NULL, NULL, NULL,
extack);
if (err)
goto err_switchdev_offload;
/* Port enters in bridge mode therefor don't need to copy to CPU
* frames for multicast in case the bridge is not requesting them
*/
__dev_mc_unsync(port->ndev, sparx5_mc_unsync);
__dev_mc_unsync(ndev, sparx5_mc_unsync);
return 0;
err_switchdev_offload:
clear_bit(port->portno, sparx5->bridge_mask);
return err;
}
static void sparx5_port_bridge_leave(struct sparx5_port *port,
......@@ -122,6 +134,8 @@ static void sparx5_port_bridge_leave(struct sparx5_port *port,
{
struct sparx5 *sparx5 = port->sparx5;
switchdev_bridge_port_unoffload(port->ndev, NULL, NULL, NULL);
clear_bit(port->portno, sparx5->bridge_mask);
if (bitmap_empty(sparx5->bridge_mask, SPX5_PORTS))
sparx5->hw_bridge_dev = NULL;
......@@ -139,11 +153,15 @@ static int sparx5_port_changeupper(struct net_device *dev,
struct netdev_notifier_changeupper_info *info)
{
struct sparx5_port *port = netdev_priv(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)) {
if (info->linking)
err = sparx5_port_bridge_join(port, info->upper_dev);
err = sparx5_port_bridge_join(port, info->upper_dev,
extack);
else
sparx5_port_bridge_leave(port, info->upper_dev);
......
......@@ -1154,38 +1154,19 @@ static int ocelot_switchdev_sync(struct ocelot *ocelot, int port,
struct net_device *bridge_dev,
struct netlink_ext_ack *extack)
{
struct ocelot_port *ocelot_port = ocelot->ports[port];
struct ocelot_port_private *priv;
clock_t ageing_time;
u8 stp_state;
int err;
priv = container_of(ocelot_port, struct ocelot_port_private, port);
ocelot_inherit_brport_flags(ocelot, port, brport_dev);
stp_state = br_port_get_stp_state(brport_dev);
ocelot_bridge_stp_state_set(ocelot, port, stp_state);
err = ocelot_port_vlan_filtering(ocelot, port,
br_vlan_enabled(bridge_dev));
if (err)
return err;
ageing_time = br_get_ageing_time(bridge_dev);
ocelot_port_attr_ageing_set(ocelot, port, ageing_time);
err = br_mdb_replay(bridge_dev, brport_dev, priv, true,
&ocelot_switchdev_blocking_nb, extack);
if (err && err != -EOPNOTSUPP)
return err;
err = br_vlan_replay(bridge_dev, brport_dev, priv, true,
&ocelot_switchdev_blocking_nb, extack);
if (err && err != -EOPNOTSUPP)
return err;
return 0;
return ocelot_port_vlan_filtering(ocelot, port,
br_vlan_enabled(bridge_dev));
}
static int ocelot_switchdev_unsync(struct ocelot *ocelot, int port)
......@@ -1216,6 +1197,13 @@ static int ocelot_netdevice_bridge_join(struct net_device *dev,
ocelot_port_bridge_join(ocelot, port, bridge);
err = switchdev_bridge_port_offload(brport_dev, dev, priv,
&ocelot_netdevice_nb,
&ocelot_switchdev_blocking_nb,
extack);
if (err)
goto err_switchdev_offload;
err = ocelot_switchdev_sync(ocelot, port, brport_dev, bridge, extack);
if (err)
goto err_switchdev_sync;
......@@ -1223,10 +1211,24 @@ static int ocelot_netdevice_bridge_join(struct net_device *dev,
return 0;
err_switchdev_sync:
switchdev_bridge_port_unoffload(brport_dev, priv,
&ocelot_netdevice_nb,
&ocelot_switchdev_blocking_nb);
err_switchdev_offload:
ocelot_port_bridge_leave(ocelot, port, bridge);
return err;
}
static void ocelot_netdevice_pre_bridge_leave(struct net_device *dev,
struct net_device *brport_dev)
{
struct ocelot_port_private *priv = netdev_priv(dev);
switchdev_bridge_port_unoffload(brport_dev, priv,
&ocelot_netdevice_nb,
&ocelot_switchdev_blocking_nb);
}
static int ocelot_netdevice_bridge_leave(struct net_device *dev,
struct net_device *brport_dev,
struct net_device *bridge)
......@@ -1279,6 +1281,18 @@ static int ocelot_netdevice_lag_join(struct net_device *dev,
return err;
}
static void ocelot_netdevice_pre_lag_leave(struct net_device *dev,
struct net_device *bond)
{
struct net_device *bridge_dev;
bridge_dev = netdev_master_upper_dev_get(bond);
if (!bridge_dev || !netif_is_bridge_master(bridge_dev))
return;
ocelot_netdevice_pre_bridge_leave(dev, bond);
}
static int ocelot_netdevice_lag_leave(struct net_device *dev,
struct net_device *bond)
{
......@@ -1355,6 +1369,43 @@ ocelot_netdevice_lag_changeupper(struct net_device *dev,
return NOTIFY_DONE;
}
static int
ocelot_netdevice_prechangeupper(struct net_device *dev,
struct net_device *brport_dev,
struct netdev_notifier_changeupper_info *info)
{
if (netif_is_bridge_master(info->upper_dev) && !info->linking)
ocelot_netdevice_pre_bridge_leave(dev, brport_dev);
if (netif_is_lag_master(info->upper_dev) && !info->linking)
ocelot_netdevice_pre_lag_leave(dev, info->upper_dev);
return NOTIFY_DONE;
}
static int
ocelot_netdevice_lag_prechangeupper(struct net_device *dev,
struct netdev_notifier_changeupper_info *info)
{
struct net_device *lower;
struct list_head *iter;
int err = NOTIFY_DONE;
netdev_for_each_lower_dev(dev, lower, iter) {
struct ocelot_port_private *priv = netdev_priv(lower);
struct ocelot_port *ocelot_port = &priv->port;
if (ocelot_port->bond != dev)
return NOTIFY_OK;
err = ocelot_netdevice_prechangeupper(dev, lower, info);
if (err)
return err;
}
return NOTIFY_DONE;
}
static int
ocelot_netdevice_changelowerstate(struct net_device *dev,
struct netdev_lag_lower_state_info *info)
......@@ -1382,6 +1433,17 @@ static int ocelot_netdevice_event(struct notifier_block *unused,
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
switch (event) {
case NETDEV_PRECHANGEUPPER: {
struct netdev_notifier_changeupper_info *info = ptr;
if (ocelot_netdevice_dev_check(dev))
return ocelot_netdevice_prechangeupper(dev, dev, info);
if (netif_is_lag_master(dev))
return ocelot_netdevice_lag_prechangeupper(dev, info);
break;
}
case NETDEV_CHANGEUPPER: {
struct netdev_notifier_changeupper_info *info = ptr;
......
......@@ -119,7 +119,8 @@ struct rocker_world_ops {
int (*port_obj_fdb_del)(struct rocker_port *rocker_port,
u16 vid, const unsigned char *addr);
int (*port_master_linked)(struct rocker_port *rocker_port,
struct net_device *master);
struct net_device *master,
struct netlink_ext_ack *extack);
int (*port_master_unlinked)(struct rocker_port *rocker_port,
struct net_device *master);
int (*port_neigh_update)(struct rocker_port *rocker_port,
......
......@@ -1670,13 +1670,14 @@ rocker_world_port_fdb_del(struct rocker_port *rocker_port,
}
static int rocker_world_port_master_linked(struct rocker_port *rocker_port,
struct net_device *master)
struct net_device *master,
struct netlink_ext_ack *extack)
{
struct rocker_world_ops *wops = rocker_port->rocker->wops;
if (!wops->port_master_linked)
return -EOPNOTSUPP;
return wops->port_master_linked(rocker_port, master);
return wops->port_master_linked(rocker_port, master, extack);
}
static int rocker_world_port_master_unlinked(struct rocker_port *rocker_port,
......@@ -3107,6 +3108,7 @@ struct rocker_port *rocker_port_dev_lower_find(struct net_device *dev,
static int rocker_netdevice_event(struct notifier_block *unused,
unsigned long event, void *ptr)
{
struct netlink_ext_ack *extack = netdev_notifier_info_to_extack(ptr);
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
struct netdev_notifier_changeupper_info *info;
struct rocker_port *rocker_port;
......@@ -3123,7 +3125,8 @@ static int rocker_netdevice_event(struct notifier_block *unused,
rocker_port = netdev_priv(dev);
if (info->linking) {
err = rocker_world_port_master_linked(rocker_port,
info->upper_dev);
info->upper_dev,
extack);
if (err)
netdev_warn(dev, "failed to reflect master linked (err %d)\n",
err);
......
......@@ -2571,8 +2571,10 @@ static int ofdpa_port_obj_fdb_del(struct rocker_port *rocker_port,
}
static int ofdpa_port_bridge_join(struct ofdpa_port *ofdpa_port,
struct net_device *bridge)
struct net_device *bridge,
struct netlink_ext_ack *extack)
{
struct net_device *dev = ofdpa_port->dev;
int err;
/* Port is joining bridge, so the internal VLAN for the
......@@ -2592,13 +2594,21 @@ static int ofdpa_port_bridge_join(struct ofdpa_port *ofdpa_port,
ofdpa_port->bridge_dev = bridge;
return ofdpa_port_vlan_add(ofdpa_port, OFDPA_UNTAGGED_VID, 0);
err = ofdpa_port_vlan_add(ofdpa_port, OFDPA_UNTAGGED_VID, 0);
if (err)
return err;
return switchdev_bridge_port_offload(dev, dev, NULL, NULL, NULL,
extack);
}
static int ofdpa_port_bridge_leave(struct ofdpa_port *ofdpa_port)
{
struct net_device *dev = ofdpa_port->dev;
int err;
switchdev_bridge_port_unoffload(dev, NULL, NULL, NULL);
err = ofdpa_port_vlan_del(ofdpa_port, OFDPA_UNTAGGED_VID, 0);
if (err)
return err;
......@@ -2637,13 +2647,14 @@ static int ofdpa_port_ovs_changed(struct ofdpa_port *ofdpa_port,
}
static int ofdpa_port_master_linked(struct rocker_port *rocker_port,
struct net_device *master)
struct net_device *master,
struct netlink_ext_ack *extack)
{
struct ofdpa_port *ofdpa_port = rocker_port->wpriv;
int err = 0;
if (netif_is_bridge_master(master))
err = ofdpa_port_bridge_join(ofdpa_port, master);
err = ofdpa_port_bridge_join(ofdpa_port, master, extack);
else if (netif_is_ovs_master(master))
err = ofdpa_port_ovs_changed(ofdpa_port, master);
return err;
......
......@@ -7,6 +7,7 @@
#include <linux/clk.h>
#include <linux/etherdevice.h>
#include <linux/if_bridge.h>
#include <linux/if_vlan.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
......@@ -2077,10 +2078,13 @@ bool am65_cpsw_port_dev_check(const struct net_device *ndev)
return false;
}
static int am65_cpsw_netdevice_port_link(struct net_device *ndev, struct net_device *br_ndev)
static int am65_cpsw_netdevice_port_link(struct net_device *ndev,
struct net_device *br_ndev,
struct netlink_ext_ack *extack)
{
struct am65_cpsw_common *common = am65_ndev_to_common(ndev);
struct am65_cpsw_ndev_priv *priv = am65_ndev_to_priv(ndev);
int err;
if (!common->br_members) {
common->hw_bridge_dev = br_ndev;
......@@ -2092,6 +2096,11 @@ static int am65_cpsw_netdevice_port_link(struct net_device *ndev, struct net_dev
return -EOPNOTSUPP;
}
err = switchdev_bridge_port_offload(ndev, ndev, NULL, NULL, NULL,
extack);
if (err)
return err;
common->br_members |= BIT(priv->port->port_id);
am65_cpsw_port_offload_fwd_mark_update(common);
......@@ -2104,6 +2113,8 @@ static void am65_cpsw_netdevice_port_unlink(struct net_device *ndev)
struct am65_cpsw_common *common = am65_ndev_to_common(ndev);
struct am65_cpsw_ndev_priv *priv = am65_ndev_to_priv(ndev);
switchdev_bridge_port_unoffload(ndev, NULL, NULL, NULL);
common->br_members &= ~BIT(priv->port->port_id);
am65_cpsw_port_offload_fwd_mark_update(common);
......@@ -2116,6 +2127,7 @@ static void am65_cpsw_netdevice_port_unlink(struct net_device *ndev)
static int am65_cpsw_netdevice_event(struct notifier_block *unused,
unsigned long event, void *ptr)
{
struct netlink_ext_ack *extack = netdev_notifier_info_to_extack(ptr);
struct net_device *ndev = netdev_notifier_info_to_dev(ptr);
struct netdev_notifier_changeupper_info *info;
int ret = NOTIFY_DONE;
......@@ -2129,7 +2141,9 @@ static int am65_cpsw_netdevice_event(struct notifier_block *unused,
if (netif_is_bridge_master(info->upper_dev)) {
if (info->linking)
ret = am65_cpsw_netdevice_port_link(ndev, info->upper_dev);
ret = am65_cpsw_netdevice_port_link(ndev,
info->upper_dev,
extack);
else
am65_cpsw_netdevice_port_unlink(ndev);
}
......
......@@ -11,6 +11,7 @@
#include <linux/module.h>
#include <linux/irqreturn.h>
#include <linux/interrupt.h>
#include <linux/if_bridge.h>
#include <linux/if_ether.h>
#include <linux/etherdevice.h>
#include <linux/net_tstamp.h>
......@@ -1499,10 +1500,12 @@ static void cpsw_port_offload_fwd_mark_update(struct cpsw_common *cpsw)
}
static int cpsw_netdevice_port_link(struct net_device *ndev,
struct net_device *br_ndev)
struct net_device *br_ndev,
struct netlink_ext_ack *extack)
{
struct cpsw_priv *priv = netdev_priv(ndev);
struct cpsw_common *cpsw = priv->cpsw;
int err;
if (!cpsw->br_members) {
cpsw->hw_bridge_dev = br_ndev;
......@@ -1514,6 +1517,11 @@ static int cpsw_netdevice_port_link(struct net_device *ndev,
return -EOPNOTSUPP;
}
err = switchdev_bridge_port_offload(ndev, ndev, NULL, NULL, NULL,
extack);
if (err)
return err;
cpsw->br_members |= BIT(priv->emac_port);
cpsw_port_offload_fwd_mark_update(cpsw);
......@@ -1526,6 +1534,8 @@ static void cpsw_netdevice_port_unlink(struct net_device *ndev)
struct cpsw_priv *priv = netdev_priv(ndev);
struct cpsw_common *cpsw = priv->cpsw;
switchdev_bridge_port_unoffload(ndev, NULL, NULL, NULL);
cpsw->br_members &= ~BIT(priv->emac_port);
cpsw_port_offload_fwd_mark_update(cpsw);
......@@ -1538,6 +1548,7 @@ static void cpsw_netdevice_port_unlink(struct net_device *ndev)
static int cpsw_netdevice_event(struct notifier_block *unused,
unsigned long event, void *ptr)
{
struct netlink_ext_ack *extack = netdev_notifier_info_to_extack(ptr);
struct net_device *ndev = netdev_notifier_info_to_dev(ptr);
struct netdev_notifier_changeupper_info *info;
int ret = NOTIFY_DONE;
......@@ -1552,7 +1563,8 @@ static int cpsw_netdevice_event(struct notifier_block *unused,
if (netif_is_bridge_master(info->upper_dev)) {
if (info->linking)
ret = cpsw_netdevice_port_link(ndev,
info->upper_dev);
info->upper_dev,
extack);
else
cpsw_netdevice_port_unlink(ndev);
}
......
......@@ -70,9 +70,6 @@ bool br_multicast_has_querier_adjacent(struct net_device *dev, int proto);
bool br_multicast_has_router_adjacent(struct net_device *dev, int proto);
bool br_multicast_enabled(const struct net_device *dev);
bool br_multicast_router(const struct net_device *dev);
int br_mdb_replay(struct net_device *br_dev, struct net_device *dev,
const void *ctx, bool adding, struct notifier_block *nb,
struct netlink_ext_ack *extack);
#else
static inline int br_multicast_list_adjacent(struct net_device *dev,
struct list_head *br_ip_list)
......@@ -104,13 +101,6 @@ static inline bool br_multicast_router(const struct net_device *dev)
{
return false;
}
static inline int br_mdb_replay(const struct net_device *br_dev,
const struct net_device *dev, const void *ctx,
bool adding, struct notifier_block *nb,
struct netlink_ext_ack *extack)
{
return -EOPNOTSUPP;
}
#endif
#if IS_ENABLED(CONFIG_BRIDGE) && IS_ENABLED(CONFIG_BRIDGE_VLAN_FILTERING)
......@@ -120,9 +110,6 @@ int br_vlan_get_pvid_rcu(const struct net_device *dev, u16 *p_pvid);
int br_vlan_get_proto(const struct net_device *dev, u16 *p_proto);
int br_vlan_get_info(const struct net_device *dev, u16 vid,
struct bridge_vlan_info *p_vinfo);
int br_vlan_replay(struct net_device *br_dev, struct net_device *dev,
const void *ctx, bool adding, struct notifier_block *nb,
struct netlink_ext_ack *extack);
#else
static inline bool br_vlan_enabled(const struct net_device *dev)
{
......@@ -149,14 +136,6 @@ static inline int br_vlan_get_info(const struct net_device *dev, u16 vid,
{
return -EINVAL;
}
static inline int br_vlan_replay(struct net_device *br_dev,
struct net_device *dev, const void *ctx,
bool adding, struct notifier_block *nb,
struct netlink_ext_ack *extack)
{
return -EOPNOTSUPP;
}
#endif
#if IS_ENABLED(CONFIG_BRIDGE)
......@@ -167,8 +146,6 @@ void br_fdb_clear_offload(const struct net_device *dev, u16 vid);
bool br_port_flag_is_set(const struct net_device *dev, unsigned long flag);
u8 br_port_get_stp_state(const struct net_device *dev);
clock_t br_get_ageing_time(const struct net_device *br_dev);
int br_fdb_replay(const struct net_device *br_dev, const struct net_device *dev,
const void *ctx, bool adding, struct notifier_block *nb);
#else
static inline struct net_device *
br_fdb_find_port(const struct net_device *br_dev,
......@@ -197,12 +174,38 @@ static inline clock_t br_get_ageing_time(const struct net_device *br_dev)
{
return 0;
}
#endif
#if IS_ENABLED(CONFIG_BRIDGE) && IS_ENABLED(CONFIG_NET_SWITCHDEV)
int switchdev_bridge_port_offload(struct net_device *brport_dev,
struct net_device *dev, const void *ctx,
struct notifier_block *atomic_nb,
struct notifier_block *blocking_nb,
struct netlink_ext_ack *extack);
void switchdev_bridge_port_unoffload(struct net_device *brport_dev,
const void *ctx,
struct notifier_block *atomic_nb,
struct notifier_block *blocking_nb);
#else
static inline int
switchdev_bridge_port_offload(struct net_device *brport_dev,
struct net_device *dev, const void *ctx,
struct notifier_block *atomic_nb,
struct notifier_block *blocking_nb,
struct netlink_ext_ack *extack)
{
return -EINVAL;
}
static inline int br_fdb_replay(const struct net_device *br_dev,
const struct net_device *dev, const void *ctx,
bool adding, struct notifier_block *nb)
static inline void
switchdev_bridge_port_unoffload(struct net_device *brport_dev,
const void *ctx,
struct notifier_block *atomic_nb,
struct notifier_block *blocking_nb)
{
return -EOPNOTSUPP;
}
#endif
......
......@@ -760,6 +760,9 @@ int br_fdb_replay(const struct net_device *br_dev, const struct net_device *dev,
unsigned long action;
int err = 0;
if (!nb)
return 0;
if (!netif_is_bridge_master(br_dev))
return -EINVAL;
......@@ -792,7 +795,6 @@ int br_fdb_replay(const struct net_device *br_dev, const struct net_device *dev,
return err;
}
EXPORT_SYMBOL_GPL(br_fdb_replay);
static void fdb_notify(struct net_bridge *br,
const struct net_bridge_fdb_entry *fdb, int type,
......
......@@ -643,10 +643,6 @@ int br_add_if(struct net_bridge *br, struct net_device *dev,
if (err)
goto err5;
err = nbp_switchdev_mark_set(p);
if (err)
goto err6;
dev_disable_lro(dev);
list_add_rcu(&p->list, &br->port_list);
......@@ -684,13 +680,13 @@ int br_add_if(struct net_bridge *br, struct net_device *dev,
*/
err = dev_pre_changeaddr_notify(br->dev, dev->dev_addr, extack);
if (err)
goto err7;
goto err6;
}
err = nbp_vlan_init(p, extack);
if (err) {
netdev_err(dev, "failed to initialize vlan filtering on this port\n");
goto err7;
goto err6;
}
spin_lock_bh(&br->lock);
......@@ -713,13 +709,12 @@ int br_add_if(struct net_bridge *br, struct net_device *dev,
return 0;
err7:
err6:
if (fdb_synced)
br_fdb_unsync_static(br, p);
list_del_rcu(&p->list);
br_fdb_delete_by_port(br, p, 0, 1);
nbp_update_port_count(br);
err6:
netdev_upper_dev_unlink(dev, br->dev);
err5:
dev->priv_flags &= ~IFF_BRIDGE_PORT;
......
......@@ -617,6 +617,9 @@ int br_mdb_replay(struct net_device *br_dev, struct net_device *dev,
ASSERT_RTNL();
if (!nb)
return 0;
if (!netif_is_bridge_master(br_dev) || !netif_is_bridge_port(dev))
return -EINVAL;
......@@ -686,7 +689,6 @@ int br_mdb_replay(struct net_device *br_dev, struct net_device *dev,
return err;
}
EXPORT_SYMBOL_GPL(br_mdb_replay);
static void br_mdb_switchdev_host_port(struct net_device *dev,
struct net_device *lower_dev,
......
......@@ -29,6 +29,8 @@
#define BR_MULTICAST_DEFAULT_HASH_MAX 4096
#define BR_HWDOM_MAX BITS_PER_LONG
#define BR_VERSION "2.3"
/* Control of forwarding link local multicast */
......@@ -386,7 +388,12 @@ struct net_bridge_port {
struct netpoll *np;
#endif
#ifdef CONFIG_NET_SWITCHDEV
int offload_fwd_mark;
/* Identifier used to group ports that share the same switchdev
* hardware domain.
*/
int hwdom;
int offload_count;
struct netdev_phys_item_id ppid;
#endif
u16 group_fwd_mask;
u16 backup_redirected_cnt;
......@@ -510,7 +517,12 @@ struct net_bridge {
u32 auto_cnt;
#ifdef CONFIG_NET_SWITCHDEV
int offload_fwd_mark;
/* Counter used to make sure that hardware domains get unique
* identifiers in case a bridge spans multiple switchdev instances.
*/
int last_hwdom;
/* Bit mask of hardware domain numbers in use */
unsigned long busy_hwdoms;
#endif
struct hlist_head fdb_list;
......@@ -540,7 +552,12 @@ struct br_input_skb_cb {
#endif
#ifdef CONFIG_NET_SWITCHDEV
int offload_fwd_mark;
/* The switchdev hardware domain from which this packet was received.
* If skb->offload_fwd_mark was set, then this packet was already
* forwarded by hardware to the other ports in the source hardware
* domain, otherwise it wasn't.
*/
int src_hwdom;
#endif
};
......@@ -752,6 +769,8 @@ int br_fdb_external_learn_del(struct net_bridge *br, struct net_bridge_port *p,
bool swdev_notify);
void br_fdb_offloaded_set(struct net_bridge *br, struct net_bridge_port *p,
const unsigned char *addr, u16 vid, bool offloaded);
int br_fdb_replay(const struct net_device *br_dev, const struct net_device *dev,
const void *ctx, bool adding, struct notifier_block *nb);
/* br_forward.c */
enum br_pkt_type {
......@@ -912,6 +931,10 @@ int br_multicast_toggle_vlan_snooping(struct net_bridge *br, bool on,
struct netlink_ext_ack *extack);
bool br_multicast_toggle_global_vlan(struct net_bridge_vlan *vlan, bool on);
int br_mdb_replay(struct net_device *br_dev, struct net_device *dev,
const void *ctx, bool adding, struct notifier_block *nb,
struct netlink_ext_ack *extack);
static inline bool br_group_is_l2(const struct br_ip *group)
{
return group->proto == 0;
......@@ -1290,6 +1313,14 @@ static inline bool br_multicast_toggle_global_vlan(struct net_bridge_vlan *vlan,
{
return false;
}
static inline int br_mdb_replay(struct net_device *br_dev,
struct net_device *dev, const void *ctx,
bool adding, struct notifier_block *nb,
struct netlink_ext_ack *extack)
{
return -EOPNOTSUPP;
}
#endif
/* br_vlan.c */
......@@ -1341,6 +1372,9 @@ void br_vlan_notify(const struct net_bridge *br,
const struct net_bridge_port *p,
u16 vid, u16 vid_range,
int cmd);
int br_vlan_replay(struct net_device *br_dev, struct net_device *dev,
const void *ctx, bool adding, struct notifier_block *nb,
struct netlink_ext_ack *extack);
bool br_vlan_can_enter_range(const struct net_bridge_vlan *v_curr,
const struct net_bridge_vlan *range_end);
......@@ -1586,6 +1620,14 @@ static inline bool br_vlan_can_enter_range(const struct net_bridge_vlan *v_curr,
{
return true;
}
static inline int br_vlan_replay(struct net_device *br_dev,
struct net_device *dev, const void *ctx,
bool adding, struct notifier_block *nb,
struct netlink_ext_ack *extack)
{
return -EOPNOTSUPP;
}
#endif
/* br_vlan_options.c */
......@@ -1829,7 +1871,6 @@ static inline void br_sysfs_delbr(struct net_device *dev) { return; }
/* br_switchdev.c */
#ifdef CONFIG_NET_SWITCHDEV
int nbp_switchdev_mark_set(struct net_bridge_port *p);
void nbp_switchdev_frame_mark(const struct net_bridge_port *p,
struct sk_buff *skb);
bool nbp_switchdev_allowed_egress(const struct net_bridge_port *p,
......@@ -1843,17 +1884,13 @@ void br_switchdev_fdb_notify(struct net_bridge *br,
int br_switchdev_port_vlan_add(struct net_device *dev, u16 vid, u16 flags,
struct netlink_ext_ack *extack);
int br_switchdev_port_vlan_del(struct net_device *dev, u16 vid);
void br_switchdev_init(struct net_bridge *br);
static inline void br_switchdev_frame_unmark(struct sk_buff *skb)
{
skb->offload_fwd_mark = 0;
}
#else
static inline int nbp_switchdev_mark_set(struct net_bridge_port *p)
{
return 0;
}
static inline void nbp_switchdev_frame_mark(const struct net_bridge_port *p,
struct sk_buff *skb)
{
......@@ -1894,6 +1931,11 @@ br_switchdev_fdb_notify(struct net_bridge *br,
static inline void br_switchdev_frame_unmark(struct sk_buff *skb)
{
}
static inline void br_switchdev_init(struct net_bridge *br)
{
}
#endif /* CONFIG_NET_SWITCHDEV */
/* br_arp_nd_proxy.c */
......
......@@ -8,50 +8,18 @@
#include "br_private.h"
static int br_switchdev_mark_get(struct net_bridge *br, struct net_device *dev)
{
struct net_bridge_port *p;
/* dev is yet to be added to the port list. */
list_for_each_entry(p, &br->port_list, list) {
if (netdev_port_same_parent_id(dev, p->dev))
return p->offload_fwd_mark;
}
return ++br->offload_fwd_mark;
}
int nbp_switchdev_mark_set(struct net_bridge_port *p)
{
struct netdev_phys_item_id ppid = { };
int err;
ASSERT_RTNL();
err = dev_get_port_parent_id(p->dev, &ppid, true);
if (err) {
if (err == -EOPNOTSUPP)
return 0;
return err;
}
p->offload_fwd_mark = br_switchdev_mark_get(p->br, p->dev);
return 0;
}
void nbp_switchdev_frame_mark(const struct net_bridge_port *p,
struct sk_buff *skb)
{
if (skb->offload_fwd_mark && !WARN_ON_ONCE(!p->offload_fwd_mark))
BR_INPUT_SKB_CB(skb)->offload_fwd_mark = p->offload_fwd_mark;
if (p->hwdom)
BR_INPUT_SKB_CB(skb)->src_hwdom = p->hwdom;
}
bool nbp_switchdev_allowed_egress(const struct net_bridge_port *p,
const struct sk_buff *skb)
{
return !skb->offload_fwd_mark ||
BR_INPUT_SKB_CB(skb)->offload_fwd_mark != p->offload_fwd_mark;
BR_INPUT_SKB_CB(skb)->src_hwdom != p->hwdom;
}
/* Flags that can be offloaded to hardware */
......@@ -156,3 +124,192 @@ int br_switchdev_port_vlan_del(struct net_device *dev, u16 vid)
return switchdev_port_obj_del(dev, &v.obj);
}
static int nbp_switchdev_hwdom_set(struct net_bridge_port *joining)
{
struct net_bridge *br = joining->br;
struct net_bridge_port *p;
int hwdom;
/* joining is yet to be added to the port list. */
list_for_each_entry(p, &br->port_list, list) {
if (netdev_phys_item_id_same(&joining->ppid, &p->ppid)) {
joining->hwdom = p->hwdom;
return 0;
}
}
hwdom = find_next_zero_bit(&br->busy_hwdoms, BR_HWDOM_MAX, 1);
if (hwdom >= BR_HWDOM_MAX)
return -EBUSY;
set_bit(hwdom, &br->busy_hwdoms);
joining->hwdom = hwdom;
return 0;
}
static void nbp_switchdev_hwdom_put(struct net_bridge_port *leaving)
{
struct net_bridge *br = leaving->br;
struct net_bridge_port *p;
/* leaving is no longer in the port list. */
list_for_each_entry(p, &br->port_list, list) {
if (p->hwdom == leaving->hwdom)
return;
}
clear_bit(leaving->hwdom, &br->busy_hwdoms);
}
static int nbp_switchdev_add(struct net_bridge_port *p,
struct netdev_phys_item_id ppid,
struct netlink_ext_ack *extack)
{
if (p->offload_count) {
/* Prevent unsupported configurations such as a bridge port
* which is a bonding interface, and the member ports are from
* different hardware switches.
*/
if (!netdev_phys_item_id_same(&p->ppid, &ppid)) {
NL_SET_ERR_MSG_MOD(extack,
"Same bridge port cannot be offloaded by two physical switches");
return -EBUSY;
}
/* Tolerate drivers that call switchdev_bridge_port_offload()
* more than once for the same bridge port, such as when the
* bridge port is an offloaded bonding/team interface.
*/
p->offload_count++;
return 0;
}
p->ppid = ppid;
p->offload_count = 1;
return nbp_switchdev_hwdom_set(p);
}
static void nbp_switchdev_del(struct net_bridge_port *p)
{
if (WARN_ON(!p->offload_count))
return;
p->offload_count--;
if (p->offload_count)
return;
if (p->hwdom)
nbp_switchdev_hwdom_put(p);
}
static int nbp_switchdev_sync_objs(struct net_bridge_port *p, const void *ctx,
struct notifier_block *atomic_nb,
struct notifier_block *blocking_nb,
struct netlink_ext_ack *extack)
{
struct net_device *br_dev = p->br->dev;
struct net_device *dev = p->dev;
int err;
err = br_vlan_replay(br_dev, dev, ctx, true, blocking_nb, extack);
if (err && err != -EOPNOTSUPP)
return err;
err = br_mdb_replay(br_dev, dev, ctx, true, blocking_nb, extack);
if (err && err != -EOPNOTSUPP)
return err;
/* Forwarding and termination FDB entries on the port */
err = br_fdb_replay(br_dev, dev, ctx, true, atomic_nb);
if (err && err != -EOPNOTSUPP)
return err;
/* Termination FDB entries on the bridge itself */
err = br_fdb_replay(br_dev, br_dev, ctx, true, atomic_nb);
if (err && err != -EOPNOTSUPP)
return err;
return 0;
}
static void nbp_switchdev_unsync_objs(struct net_bridge_port *p,
const void *ctx,
struct notifier_block *atomic_nb,
struct notifier_block *blocking_nb)
{
struct net_device *br_dev = p->br->dev;
struct net_device *dev = p->dev;
br_vlan_replay(br_dev, dev, ctx, false, blocking_nb, NULL);
br_mdb_replay(br_dev, dev, ctx, false, blocking_nb, NULL);
/* Forwarding and termination FDB entries on the port */
br_fdb_replay(br_dev, dev, ctx, false, atomic_nb);
/* Termination FDB entries on the bridge itself */
br_fdb_replay(br_dev, br_dev, ctx, false, atomic_nb);
}
/* Let the bridge know that this port is offloaded, so that it can assign a
* switchdev hardware domain to it.
*/
int switchdev_bridge_port_offload(struct net_device *brport_dev,
struct net_device *dev, const void *ctx,
struct notifier_block *atomic_nb,
struct notifier_block *blocking_nb,
struct netlink_ext_ack *extack)
{
struct netdev_phys_item_id ppid;
struct net_bridge_port *p;
int err;
ASSERT_RTNL();
p = br_port_get_rtnl(brport_dev);
if (!p)
return -ENODEV;
err = dev_get_port_parent_id(dev, &ppid, false);
if (err)
return err;
err = nbp_switchdev_add(p, ppid, extack);
if (err)
return err;
err = nbp_switchdev_sync_objs(p, ctx, atomic_nb, blocking_nb, extack);
if (err)
goto out_switchdev_del;
return 0;
out_switchdev_del:
nbp_switchdev_del(p);
return err;
}
EXPORT_SYMBOL_GPL(switchdev_bridge_port_offload);
void switchdev_bridge_port_unoffload(struct net_device *brport_dev,
const void *ctx,
struct notifier_block *atomic_nb,
struct notifier_block *blocking_nb)
{
struct net_bridge_port *p;
ASSERT_RTNL();
p = br_port_get_rtnl(brport_dev);
if (!p)
return;
nbp_switchdev_unsync_objs(p, ctx, atomic_nb, blocking_nb);
nbp_switchdev_del(p);
}
EXPORT_SYMBOL_GPL(switchdev_bridge_port_unoffload);
......@@ -1856,6 +1856,9 @@ int br_vlan_replay(struct net_device *br_dev, struct net_device *dev,
ASSERT_RTNL();
if (!nb)
return 0;
if (!netif_is_bridge_master(br_dev))
return -EINVAL;
......@@ -1902,7 +1905,6 @@ int br_vlan_replay(struct net_device *br_dev, struct net_device *dev,
return err;
}
EXPORT_SYMBOL_GPL(br_vlan_replay);
/* check if v_curr can enter a range ending in range_end */
bool br_vlan_can_enter_range(const struct net_bridge_vlan *v_curr,
......
......@@ -204,16 +204,14 @@ 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_pre_bridge_leave(struct dsa_port *dp, struct net_device *br);
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_pre_lag_leave(struct dsa_port *dp, struct net_device *lag_dev);
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);
......
......@@ -167,8 +167,8 @@ static void dsa_port_clear_brport_flags(struct dsa_port *dp)
}
}
static int dsa_port_switchdev_sync(struct dsa_port *dp,
struct netlink_ext_ack *extack)
static int dsa_port_switchdev_sync_attrs(struct dsa_port *dp,
struct netlink_ext_ack *extack)
{
struct net_device *brport_dev = dsa_port_to_bridge_port(dp);
struct net_device *br = dp->bridge_dev;
......@@ -194,59 +194,6 @@ static int dsa_port_switchdev_sync(struct dsa_port *dp,
if (err && err != -EOPNOTSUPP)
return err;
err = br_mdb_replay(br, brport_dev, dp, true,
&dsa_slave_switchdev_blocking_notifier, extack);
if (err && err != -EOPNOTSUPP)
return err;
/* Forwarding and termination FDB entries on the port */
err = br_fdb_replay(br, brport_dev, dp, true,
&dsa_slave_switchdev_notifier);
if (err && err != -EOPNOTSUPP)
return err;
/* Termination FDB entries on the bridge itself */
err = br_fdb_replay(br, br, dp, true, &dsa_slave_switchdev_notifier);
if (err && err != -EOPNOTSUPP)
return err;
err = br_vlan_replay(br, brport_dev, dp, true,
&dsa_slave_switchdev_blocking_notifier, extack);
if (err && err != -EOPNOTSUPP)
return err;
return 0;
}
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;
/* Forwarding and termination FDB entries on the port */
err = br_fdb_replay(br, brport_dev, dp, false,
&dsa_slave_switchdev_notifier);
if (err && err != -EOPNOTSUPP)
return err;
/* Termination FDB entries on the bridge itself */
err = br_fdb_replay(br, br, 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;
}
......@@ -292,6 +239,8 @@ int dsa_port_bridge_join(struct dsa_port *dp, struct net_device *br,
.port = dp->index,
.br = br,
};
struct net_device *dev = dp->slave;
struct net_device *brport_dev;
int err;
/* Here the interface is already bridged. Reflect the current
......@@ -299,16 +248,29 @@ int dsa_port_bridge_join(struct dsa_port *dp, struct net_device *br,
*/
dp->bridge_dev = br;
brport_dev = dsa_port_to_bridge_port(dp);
err = dsa_broadcast(DSA_NOTIFIER_BRIDGE_JOIN, &info);
if (err)
goto out_rollback;
err = dsa_port_switchdev_sync(dp, extack);
err = switchdev_bridge_port_offload(brport_dev, dev, dp,
&dsa_slave_switchdev_notifier,
&dsa_slave_switchdev_blocking_notifier,
extack);
if (err)
goto out_rollback_unbridge;
err = dsa_port_switchdev_sync_attrs(dp, extack);
if (err)
goto out_rollback_unoffload;
return 0;
out_rollback_unoffload:
switchdev_bridge_port_unoffload(brport_dev, dp,
&dsa_slave_switchdev_notifier,
&dsa_slave_switchdev_blocking_notifier);
out_rollback_unbridge:
dsa_broadcast(DSA_NOTIFIER_BRIDGE_LEAVE, &info);
out_rollback:
......@@ -316,10 +278,13 @@ 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)
void dsa_port_pre_bridge_leave(struct dsa_port *dp, struct net_device *br)
{
return dsa_port_switchdev_unsync_objs(dp, br, extack);
struct net_device *brport_dev = dsa_port_to_bridge_port(dp);
switchdev_bridge_port_unoffload(brport_dev, dp,
&dsa_slave_switchdev_notifier,
&dsa_slave_switchdev_blocking_notifier);
}
void dsa_port_bridge_leave(struct dsa_port *dp, struct net_device *br)
......@@ -409,13 +374,10 @@ 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)
void dsa_port_pre_lag_leave(struct dsa_port *dp, struct net_device *lag)
{
if (dp->bridge_dev)
return dsa_port_pre_bridge_leave(dp, dp->bridge_dev, extack);
return 0;
dsa_port_pre_bridge_leave(dp, dp->bridge_dev);
}
void dsa_port_lag_leave(struct dsa_port *dp, struct net_device *lag)
......
......@@ -2052,20 +2052,16 @@ 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);
dsa_port_pre_bridge_leave(dp, info->upper_dev);
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_lag_leave(dp, info->upper_dev);
/* dsa_port_pre_hsr_leave is not yet necessary since hsr cannot be
* meaningfully enslaved to a bridge yet
*/
return notifier_from_errno(err);
return NOTIFY_DONE;
}
static int
......
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