Skip to content

Commit

Permalink
net: bridge: switchdev: let drivers inform which bridge ports are off…
Browse files Browse the repository at this point in the history
…loaded

On reception of an skb, the bridge checks if it was marked as 'already
forwarded in hardware' (checks if skb->offload_fwd_mark == 1), and if it
is, it assigns the source hardware domain of that skb based on the
hardware domain of the ingress port. Then during forwarding, it enforces
that the egress port must have a different hardware domain than the
ingress one (this is done in nbp_switchdev_allowed_egress).

Non-switchdev drivers don't report any physical switch id (neither
through devlink nor .ndo_get_port_parent_id), therefore the bridge
assigns them a hardware domain of 0, and packets coming from them will
always have skb->offload_fwd_mark = 0. So there aren't any restrictions.

Problems appear due to the fact that DSA would like to perform software
fallback for bonding and team interfaces that the physical switch cannot
offload.

       +-- br0 ---+
      / /   |      \
     / /    |       \
    /  |    |      bond0
   /   |    |     /    \
 swp0 swp1 swp2 swp3 swp4

There, it is desirable that the presence of swp3 and swp4 under a
non-offloaded LAG does not preclude us from doing hardware bridging
beteen swp0, swp1 and swp2. The bandwidth of the CPU is often times high
enough that software bridging between {swp0,swp1,swp2} and bond0 is not
impractical.

But this creates an impossible paradox given the current way in which
port hardware domains are assigned. When the driver receives a packet
from swp0 (say, due to flooding), it must set skb->offload_fwd_mark to
something.

- If we set it to 0, then the bridge will forward it towards swp1, swp2
  and bond0. But the switch has already forwarded it towards swp1 and
  swp2 (not to bond0, remember, that isn't offloaded, so as far as the
  switch is concerned, ports swp3 and swp4 are not looking up the FDB,
  and the entire bond0 is a destination that is strictly behind the
  CPU). But we don't want duplicated traffic towards swp1 and swp2, so
  it's not ok to set skb->offload_fwd_mark = 0.

- If we set it to 1, then the bridge will not forward the skb towards
  the ports with the same switchdev mark, i.e. not to swp1, swp2 and
  bond0. Towards swp1 and swp2 that's ok, but towards bond0? It should
  have forwarded the skb there.

So the real issue is that bond0 will be assigned the same hardware
domain as {swp0,swp1,swp2}, because the function that assigns hardware
domains to bridge ports, nbp_switchdev_add(), recurses through bond0's
lower interfaces until it finds something that implements devlink (calls
dev_get_port_parent_id with bool recurse = true). This is a problem
because the fact that bond0 can be offloaded by swp3 and swp4 in our
example is merely an assumption.

A solution is to give the bridge explicit hints as to what hardware
domain it should use for each port.

Currently, the bridging offload is very 'silent': a driver registers a
netdevice notifier, which is put on the netns's notifier chain, and
which sniffs around for NETDEV_CHANGEUPPER events where the upper is a
bridge, and the lower is an interface it knows about (one registered by
this driver, normally). Then, from within that notifier, it does a bunch
of stuff behind the bridge's back, without the bridge necessarily
knowing that there's somebody offloading that port. It looks like this:

     ip link set swp0 master br0
                  |
                  v
 br_add_if() calls netdev_master_upper_dev_link()
                  |
                  v
        call_netdevice_notifiers
                  |
                  v
       dsa_slave_netdevice_event
                  |
                  v
        oh, hey! it's for me!
                  |
                  v
           .port_bridge_join

What we do to solve the conundrum is to be less silent, and change the
switchdev drivers to present themselves to the bridge. Something like this:

     ip link set swp0 master br0
                  |
                  v
 br_add_if() calls netdev_master_upper_dev_link()
                  |
                  v                    bridge: Aye! I'll use this
        call_netdevice_notifiers           ^  ppid as the
                  |                        |  hardware domain for
                  v                        |  this port, and zero
       dsa_slave_netdevice_event           |  if I got nothing.
                  |                        |
                  v                        |
        oh, hey! it's for me!              |
                  |                        |
                  v                        |
           .port_bridge_join               |
                  |                        |
                  +------------------------+
             switchdev_bridge_port_offload(swp0, swp0)

Then stacked interfaces (like bond0 on top of swp3/swp4) would be
treated differently in DSA, depending on whether we can or cannot
offload them.

The offload case:

    ip link set bond0 master br0
                  |
                  v
 br_add_if() calls netdev_master_upper_dev_link()
                  |
                  v                    bridge: Aye! I'll use this
        call_netdevice_notifiers           ^  ppid as the
                  |                        |  switchdev mark for
                  v                        |        bond0.
       dsa_slave_netdevice_event           | Coincidentally (or not),
                  |                        | bond0 and swp0, swp1, swp2
                  v                        | all have the same switchdev
        hmm, it's not quite for me,        | mark now, since the ASIC
         but my driver has already         | is able to forward towards
           called .port_lag_join           | all these ports in hw.
          for it, because I have           |
      a port with dp->lag_dev == bond0.    |
                  |                        |
                  v                        |
           .port_bridge_join               |
           for swp3 and swp4               |
                  |                        |
                  +------------------------+
            switchdev_bridge_port_offload(bond0, swp3)
            switchdev_bridge_port_offload(bond0, swp4)

And the non-offload case:

    ip link set bond0 master br0
                  |
                  v
 br_add_if() calls netdev_master_upper_dev_link()
                  |
                  v                    bridge waiting:
        call_netdevice_notifiers           ^  huh, switchdev_bridge_port_offload
                  |                        |  wasn't called, okay, I'll use a
                  v                        |  hwdom of zero for this one.
       dsa_slave_netdevice_event           :  Then packets received on swp0 will
                  |                        :  not be software-forwarded towards
                  v                        :  swp1, but they will towards bond0.
         it's not for me, but
       bond0 is an upper of swp3
      and swp4, but their dp->lag_dev
       is NULL because they couldn't
            offload it.

Basically we can draw the conclusion that the lowers of a bridge port
can come and go, so depending on the configuration of lowers for a
bridge port, it can dynamically toggle between offloaded and unoffloaded.
Therefore, we need an equivalent switchdev_bridge_port_unoffload too.

This patch changes the way any switchdev driver interacts with the
bridge. From now on, everybody needs to call switchdev_bridge_port_offload
and switchdev_bridge_port_unoffload, otherwise the bridge will treat the
port as non-offloaded and allow software flooding to other ports from
the same ASIC.

Note that these functions lay the ground for a more complex handshake
between switchdev drivers and the bridge in the future.

For drivers that will request a replay of the switchdev objects when
they offload and unoffload a bridge port (DSA, dpaa2-switch, ocelot), we
place the call to switchdev_bridge_port_unoffload() strategically inside
the NETDEV_PRECHANGEUPPER notifier's code path, and not inside
NETDEV_CHANGEUPPER. This is because the switchdev object replay helpers
need the netdev adjacency lists to be valid, and that is only true in
NETDEV_PRECHANGEUPPER.

Cc: Vadym Kochan <vkochan@marvell.com>
Cc: Taras Chornyi <tchornyi@marvell.com>
Cc: Ioana Ciornei <ioana.ciornei@nxp.com>
Cc: Lars Povlsen <lars.povlsen@microchip.com>
Cc: Steen Hegelund <Steen.Hegelund@microchip.com>
Cc: UNGLinuxDriver@microchip.com
Cc: Claudiu Manoil <claudiu.manoil@nxp.com>
Cc: Alexandre Belloni <alexandre.belloni@bootlin.com>
Cc: Grygorii Strashko <grygorii.strashko@ti.com>
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Tested-by: Ioana Ciornei <ioana.ciornei@nxp.com> # dpaa2-switch: regression
Acked-by: Ioana Ciornei <ioana.ciornei@nxp.com> # dpaa2-switch
Tested-by: Horatiu Vultur <horatiu.vultur@microchip.com> # ocelot-switch
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Vladimir Oltean authored and David S. Miller committed Jul 22, 2021
1 parent 8582661 commit 2f5dc00
Show file tree
Hide file tree
Showing 17 changed files with 298 additions and 57 deletions.
13 changes: 13 additions & 0 deletions drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
Original file line number Diff line number Diff line change
Expand Up @@ -1930,8 +1930,13 @@ static int dpaa2_switch_port_bridge_join(struct net_device *netdev,
if (err)
goto err_egress_flood;

err = switchdev_bridge_port_offload(netdev, netdev, 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;
Expand All @@ -1957,6 +1962,11 @@ 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);
}

static int dpaa2_switch_port_bridge_leave(struct net_device *netdev)
{
struct ethsw_port_priv *port_priv = netdev_priv(netdev);
Expand Down Expand Up @@ -2078,6 +2088,9 @@ static int dpaa2_switch_port_netdevice_event(struct notifier_block *nb,
if (err)
goto out;

if (!info->linking)
dpaa2_switch_port_pre_bridge_leave(netdev);

break;
case NETDEV_CHANGEUPPER:
upper_dev = info->upper_dev;
Expand Down
3 changes: 2 additions & 1 deletion drivers/net/ethernet/marvell/prestera/prestera_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
11 changes: 10 additions & 1 deletion drivers/net/ethernet/marvell/prestera/prestera_switchdev.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -500,6 +501,10 @@ int prestera_bridge_port_join(struct net_device *br_dev,
goto err_brport_create;
}

err = switchdev_bridge_port_offload(br_port->dev, port->dev, extack);
if (err)
goto err_switchdev_offload;

if (bridge->vlan_enabled)
return 0;

Expand All @@ -510,6 +515,8 @@ int prestera_bridge_port_join(struct net_device *br_dev,
return 0;

err_port_join:
switchdev_bridge_port_unoffload(br_port->dev);
err_switchdev_offload:
prestera_bridge_port_put(br_port);
err_brport_create:
prestera_bridge_put(bridge);
Expand Down Expand Up @@ -584,6 +591,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);

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);
Expand Down
3 changes: 2 additions & 1 deletion drivers/net/ethernet/marvell/prestera/prestera_switchdev.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
24 changes: 19 additions & 5 deletions drivers/net/ethernet/mellanox/mlxsw/spectrum_switchdev.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
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);
list_del(&bridge_port->list);
WARN_ON(!list_empty(&bridge_port->vlans_list));
kfree(bridge_port);
Expand All @@ -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;
}

Expand Down
23 changes: 20 additions & 3 deletions drivers/net/ethernet/microchip/sparx5/sparx5_switchdev.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand All @@ -109,19 +112,29 @@ static int sparx5_port_bridge_join(struct sparx5_port *port,

set_bit(port->portno, sparx5->bridge_mask);

err = switchdev_bridge_port_offload(ndev, ndev, 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,
struct net_device *bridge)
{
struct sparx5 *sparx5 = port->sparx5;

switchdev_bridge_port_unoffload(port->ndev);

clear_bit(port->portno, sparx5->bridge_mask);
if (bitmap_empty(sparx5->bridge_mask, SPX5_PORTS))
sparx5->hw_bridge_dev = NULL;
Expand All @@ -139,11 +152,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);

Expand Down
71 changes: 71 additions & 0 deletions drivers/net/ethernet/mscc/ocelot_net.c
Original file line number Diff line number Diff line change
Expand Up @@ -1216,17 +1216,28 @@ 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, extack);
if (err)
goto err_switchdev_offload;

err = ocelot_switchdev_sync(ocelot, port, brport_dev, bridge, extack);
if (err)
goto err_switchdev_sync;

return 0;

err_switchdev_sync:
switchdev_bridge_port_unoffload(brport_dev);
err_switchdev_offload:
ocelot_port_bridge_leave(ocelot, port, bridge);
return err;
}

static void ocelot_netdevice_pre_bridge_leave(struct net_device *brport_dev)
{
switchdev_bridge_port_unoffload(brport_dev);
}

static int ocelot_netdevice_bridge_leave(struct net_device *dev,
struct net_device *brport_dev,
struct net_device *bridge)
Expand Down Expand Up @@ -1279,6 +1290,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(bond);
}

static int ocelot_netdevice_lag_leave(struct net_device *dev,
struct net_device *bond)
{
Expand Down Expand Up @@ -1355,6 +1378,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(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)
Expand Down Expand Up @@ -1382,6 +1442,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;

Expand Down
3 changes: 2 additions & 1 deletion drivers/net/ethernet/rocker/rocker.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
9 changes: 6 additions & 3 deletions drivers/net/ethernet/rocker/rocker_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down
Loading

0 comments on commit 2f5dc00

Please sign in to comment.