2021-06-24 09:07:55 +02:00
|
|
|
// SPDX-License-Identifier: GPL-2.0+
|
|
|
|
/* Microchip Sparx5 Switch driver
|
|
|
|
*
|
|
|
|
* Copyright (c) 2021 Microchip Technology Inc. and its subsidiaries.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/if_bridge.h>
|
|
|
|
#include <net/switchdev.h>
|
|
|
|
|
|
|
|
#include "sparx5_main_regs.h"
|
|
|
|
#include "sparx5_main.h"
|
|
|
|
|
|
|
|
static struct workqueue_struct *sparx5_owq;
|
|
|
|
|
|
|
|
struct sparx5_switchdev_event_work {
|
|
|
|
struct work_struct work;
|
|
|
|
struct switchdev_notifier_fdb_info fdb_info;
|
|
|
|
struct net_device *dev;
|
2022-03-14 17:09:18 +01:00
|
|
|
struct sparx5 *sparx5;
|
2021-06-24 09:07:55 +02:00
|
|
|
unsigned long event;
|
|
|
|
};
|
|
|
|
|
2022-02-23 09:27:00 +01:00
|
|
|
static int sparx5_port_attr_pre_bridge_flags(struct sparx5_port *port,
|
|
|
|
struct switchdev_brport_flags flags)
|
|
|
|
{
|
|
|
|
if (flags.mask & ~(BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-08-25 11:28:37 +02:00
|
|
|
static void sparx5_port_update_mcast_ip_flood(struct sparx5_port *port, bool flood_flag)
|
|
|
|
{
|
|
|
|
bool should_flood = flood_flag || port->is_mrouter;
|
|
|
|
int pgid;
|
|
|
|
|
|
|
|
for (pgid = PGID_IPV4_MC_DATA; pgid <= PGID_IPV6_MC_CTRL; pgid++)
|
|
|
|
sparx5_pgid_update_mask(port, pgid, should_flood);
|
|
|
|
}
|
|
|
|
|
2021-06-24 09:07:55 +02:00
|
|
|
static void sparx5_port_attr_bridge_flags(struct sparx5_port *port,
|
|
|
|
struct switchdev_brport_flags flags)
|
|
|
|
{
|
2022-08-25 11:28:37 +02:00
|
|
|
if (flags.mask & BR_MCAST_FLOOD) {
|
|
|
|
sparx5_pgid_update_mask(port, PGID_MC_FLOOD, !!(flags.val & BR_MCAST_FLOOD));
|
|
|
|
sparx5_port_update_mcast_ip_flood(port, !!(flags.val & BR_MCAST_FLOOD));
|
|
|
|
}
|
2022-02-23 09:27:00 +01:00
|
|
|
|
|
|
|
if (flags.mask & BR_FLOOD)
|
|
|
|
sparx5_pgid_update_mask(port, PGID_UC_FLOOD, !!(flags.val & BR_FLOOD));
|
|
|
|
if (flags.mask & BR_BCAST_FLOOD)
|
|
|
|
sparx5_pgid_update_mask(port, PGID_BCAST, !!(flags.val & BR_BCAST_FLOOD));
|
2021-06-24 09:07:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void sparx5_attr_stp_state_set(struct sparx5_port *port,
|
|
|
|
u8 state)
|
|
|
|
{
|
|
|
|
struct sparx5 *sparx5 = port->sparx5;
|
|
|
|
|
|
|
|
if (!test_bit(port->portno, sparx5->bridge_mask)) {
|
|
|
|
netdev_err(port->ndev,
|
|
|
|
"Controlling non-bridged port %d?\n", port->portno);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (state) {
|
|
|
|
case BR_STATE_FORWARDING:
|
|
|
|
set_bit(port->portno, sparx5->bridge_fwd_mask);
|
|
|
|
fallthrough;
|
|
|
|
case BR_STATE_LEARNING:
|
|
|
|
set_bit(port->portno, sparx5->bridge_lrn_mask);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
/* All other states treated as blocking */
|
|
|
|
clear_bit(port->portno, sparx5->bridge_fwd_mask);
|
|
|
|
clear_bit(port->portno, sparx5->bridge_lrn_mask);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* apply the bridge_fwd_mask to all the ports */
|
|
|
|
sparx5_update_fwd(sparx5);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sparx5_port_attr_ageing_set(struct sparx5_port *port,
|
|
|
|
unsigned long ageing_clock_t)
|
|
|
|
{
|
|
|
|
unsigned long ageing_jiffies = clock_t_to_jiffies(ageing_clock_t);
|
|
|
|
u32 ageing_time = jiffies_to_msecs(ageing_jiffies);
|
|
|
|
|
|
|
|
sparx5_set_ageing(port->sparx5, ageing_time);
|
|
|
|
}
|
|
|
|
|
2022-08-25 11:28:37 +02:00
|
|
|
static void sparx5_port_attr_mrouter_set(struct sparx5_port *port,
|
|
|
|
struct net_device *orig_dev,
|
|
|
|
bool enable)
|
|
|
|
{
|
|
|
|
struct sparx5 *sparx5 = port->sparx5;
|
|
|
|
struct sparx5_mdb_entry *e;
|
|
|
|
bool flood_flag;
|
|
|
|
|
|
|
|
if ((enable && port->is_mrouter) || (!enable && !port->is_mrouter))
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Add/del mrouter port on all active mdb entries in HW.
|
|
|
|
* Don't change entry port mask, since that represents
|
|
|
|
* ports that actually joined that group.
|
|
|
|
*/
|
|
|
|
mutex_lock(&sparx5->mdb_lock);
|
|
|
|
list_for_each_entry(e, &sparx5->mdb_entries, list) {
|
|
|
|
if (!test_bit(port->portno, e->port_mask) &&
|
|
|
|
ether_addr_is_ip_mcast(e->addr))
|
|
|
|
sparx5_pgid_update_mask(port, e->pgid_idx, enable);
|
|
|
|
}
|
|
|
|
mutex_unlock(&sparx5->mdb_lock);
|
|
|
|
|
|
|
|
/* Enable/disable flooding depending on if port is mrouter port
|
|
|
|
* or if mcast flood is enabled.
|
|
|
|
*/
|
|
|
|
port->is_mrouter = enable;
|
|
|
|
flood_flag = br_port_flag_is_set(port->ndev, BR_MCAST_FLOOD);
|
|
|
|
sparx5_port_update_mcast_ip_flood(port, flood_flag);
|
|
|
|
}
|
|
|
|
|
2021-06-27 14:54:24 +03:00
|
|
|
static int sparx5_port_attr_set(struct net_device *dev, const void *ctx,
|
2021-06-24 09:07:55 +02:00
|
|
|
const struct switchdev_attr *attr,
|
|
|
|
struct netlink_ext_ack *extack)
|
|
|
|
{
|
|
|
|
struct sparx5_port *port = netdev_priv(dev);
|
|
|
|
|
|
|
|
switch (attr->id) {
|
2022-02-23 09:27:00 +01:00
|
|
|
case SWITCHDEV_ATTR_ID_PORT_PRE_BRIDGE_FLAGS:
|
|
|
|
return sparx5_port_attr_pre_bridge_flags(port,
|
|
|
|
attr->u.brport_flags);
|
2021-06-24 09:07:55 +02:00
|
|
|
case SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS:
|
|
|
|
sparx5_port_attr_bridge_flags(port, attr->u.brport_flags);
|
|
|
|
break;
|
|
|
|
case SWITCHDEV_ATTR_ID_PORT_STP_STATE:
|
|
|
|
sparx5_attr_stp_state_set(port, attr->u.stp_state);
|
|
|
|
break;
|
|
|
|
case SWITCHDEV_ATTR_ID_BRIDGE_AGEING_TIME:
|
|
|
|
sparx5_port_attr_ageing_set(port, attr->u.ageing_time);
|
|
|
|
break;
|
|
|
|
case SWITCHDEV_ATTR_ID_BRIDGE_VLAN_FILTERING:
|
2022-03-18 13:53:31 +01:00
|
|
|
/* Used PVID 1 when default_pvid is 0, to avoid
|
|
|
|
* collision with non-bridged ports.
|
|
|
|
*/
|
|
|
|
if (port->pvid == 0)
|
|
|
|
port->pvid = 1;
|
2021-06-24 09:07:55 +02:00
|
|
|
port->vlan_aware = attr->u.vlan_filtering;
|
|
|
|
sparx5_vlan_port_apply(port->sparx5, port);
|
|
|
|
break;
|
2022-08-25 11:28:37 +02:00
|
|
|
case SWITCHDEV_ATTR_ID_PORT_MROUTER:
|
|
|
|
sparx5_port_attr_mrouter_set(port,
|
|
|
|
attr->orig_dev,
|
|
|
|
attr->u.mrouter);
|
|
|
|
break;
|
2021-06-24 09:07:55 +02:00
|
|
|
default:
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sparx5_port_bridge_join(struct sparx5_port *port,
|
net: bridge: switchdev: let drivers inform which bridge ports are offloaded
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>
2021-07-21 19:24:01 +03:00
|
|
|
struct net_device *bridge,
|
|
|
|
struct netlink_ext_ack *extack)
|
2021-06-24 09:07:55 +02:00
|
|
|
{
|
|
|
|
struct sparx5 *sparx5 = port->sparx5;
|
net: bridge: switchdev: let drivers inform which bridge ports are offloaded
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>
2021-07-21 19:24:01 +03:00
|
|
|
struct net_device *ndev = port->ndev;
|
|
|
|
int err;
|
2021-06-24 09:07:55 +02:00
|
|
|
|
|
|
|
if (bitmap_empty(sparx5->bridge_mask, SPX5_PORTS))
|
|
|
|
/* First bridged port */
|
|
|
|
sparx5->hw_bridge_dev = bridge;
|
|
|
|
else
|
|
|
|
if (sparx5->hw_bridge_dev != bridge)
|
|
|
|
/* This is adding the port to a second bridge, this is
|
|
|
|
* unsupported
|
|
|
|
*/
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
set_bit(port->portno, sparx5->bridge_mask);
|
|
|
|
|
net: bridge: move the switchdev object replay helpers to "push" mode
Starting with commit 4f2673b3a2b6 ("net: bridge: add helper to replay
port and host-joined mdb entries"), DSA has introduced some bridge
helpers that replay switchdev events (FDB/MDB/VLAN additions and
deletions) that can be lost by the switchdev drivers in a variety of
circumstances:
- an IP multicast group was host-joined on the bridge itself before any
switchdev port joined the bridge, leading to the host MDB entries
missing in the hardware database.
- during the bridge creation process, the MAC address of the bridge was
added to the FDB as an entry pointing towards the bridge device
itself, but with no switchdev ports being part of the bridge yet, this
local FDB entry would remain unknown to the switchdev hardware
database.
- a VLAN/FDB/MDB was added to a bridge port that is a LAG interface,
before any switchdev port joined that LAG, leading to the hardware
database missing those entries.
- a switchdev port left a LAG that is a bridge port, while the LAG
remained part of the bridge, and all FDB/MDB/VLAN entries remained
installed in the hardware database of the switchdev port.
Also, since commit 0d2cfbd41c4a ("net: bridge: ignore switchdev events
for LAG ports which didn't request replay"), DSA introduced a method,
based on a const void *ctx, to ensure that two switchdev ports under the
same LAG that is a bridge port do not see the same MDB/VLAN entry being
replayed twice by the bridge, once for every bridge port that joins the
LAG.
With so many ordering corner cases being possible, it seems unreasonable
to expect a switchdev driver writer to get it right from the first try.
Therefore, now that DSA has experimented with the bridge replay helpers
for a little bit, we can move the code to the bridge driver where it is
more readily available to all switchdev drivers.
To convert the switchdev object replay helpers from "pull mode" (where
the driver asks for them) to a "push mode" (where the bridge offers them
automatically), the biggest problem is that the bridge needs to be aware
when a switchdev port joins and leaves, even when the switchdev is only
indirectly a bridge port (for example when the bridge port is a LAG
upper of the switchdev).
Luckily, we already have a hook for that, in the form of the newly
introduced switchdev_bridge_port_offload() and
switchdev_bridge_port_unoffload() calls. These offer a natural place for
hooking the object addition and deletion replays.
Extend the above 2 functions with:
- pointers to the switchdev atomic notifier (for FDB replays) and the
blocking notifier (for MDB and VLAN replays).
- the "const void *ctx" argument required for drivers to be able to
disambiguate between which port is targeted, when multiple ports are
lowers of the same LAG that is a bridge port. Most of the drivers pass
NULL to this argument, except the ones that support LAG offload and have
the proper context check already in place in the switchdev blocking
notifier handler.
Also unexport the replay helpers, since nobody except the bridge calls
them directly now.
Note that:
(a) we abuse the terminology slightly, because FDB entries are not
"switchdev objects", but we count them as objects nonetheless.
With no direct way to prove it, I think they are not modeled as
switchdev objects because those can only be installed by the bridge
to the hardware (as opposed to FDB entries which can be propagated
in the other direction too). This is merely an abuse of terms, FDB
entries are replayed too, despite not being objects.
(b) the bridge does not attempt to sync port attributes to newly joined
ports, just the countable stuff (the objects). The reason for this
is simple: no universal and symmetric way to sync and unsync them is
known. For example, VLAN filtering: what to do on unsync, disable or
leave it enabled? Similarly, STP state, ageing timer, etc etc. What
a switchdev port does when it becomes standalone again is not really
up to the bridge's competence, and the driver should deal with it.
On the other hand, replaying deletions of switchdev objects can be
seen a matter of cleanup and therefore be treated by the bridge,
hence this patch.
We make the replay helpers opt-in for drivers, because they might not
bring immediate benefits for them:
- nbp_vlan_init() is called _after_ netdev_master_upper_dev_link(),
so br_vlan_replay() should not do anything for the new drivers on
which we call it. The existing drivers where there was even a slight
possibility for there to exist a VLAN on a bridge port before they
join it are already guarded against this: mlxsw and prestera deny
joining LAG interfaces that are members of a bridge.
- br_fdb_replay() should now notify of local FDB entries, but I patched
all drivers except DSA to ignore these new entries in commit
2c4eca3ef716 ("net: bridge: switchdev: include local flag in FDB
notifications"). Driver authors can lift this restriction as they
wish, and when they do, they can also opt into the FDB replay
functionality.
- br_mdb_replay() should fix a real issue which is described in commit
4f2673b3a2b6 ("net: bridge: add helper to replay port and host-joined
mdb entries"). However most drivers do not offload the
SWITCHDEV_OBJ_ID_HOST_MDB to see this issue: only cpsw and am65_cpsw
offload this switchdev object, and I don't completely understand the
way in which they offload this switchdev object anyway. So I'll leave
it up to these drivers' respective maintainers to opt into
br_mdb_replay().
So most of the drivers pass NULL notifier blocks for the replay helpers,
except:
- dpaa2-switch which was already acked/regression-tested with the
helpers enabled (and there isn't much of a downside in having them)
- ocelot which already had replay logic in "pull" mode
- DSA which already had replay logic in "pull" mode
An important observation is that the drivers which don't currently
request bridge event replays don't even have the
switchdev_bridge_port_{offload,unoffload} calls placed in proper places
right now. This was done to avoid unnecessary rework for drivers which
might never even add support for this. For driver writers who wish to
add replay support, this can be used as a tentative placement guide:
https://patchwork.kernel.org/project/netdevbpf/patch/20210720134655.892334-11-vladimir.oltean@nxp.com/
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>
Acked-by: Ioana Ciornei <ioana.ciornei@nxp.com> # dpaa2-switch
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-07-21 19:24:03 +03:00
|
|
|
err = switchdev_bridge_port_offload(ndev, ndev, NULL, NULL, NULL,
|
2021-07-22 18:55:38 +03:00
|
|
|
false, extack);
|
net: bridge: switchdev: let drivers inform which bridge ports are offloaded
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>
2021-07-21 19:24:01 +03:00
|
|
|
if (err)
|
|
|
|
goto err_switchdev_offload;
|
|
|
|
|
2022-03-18 13:53:31 +01:00
|
|
|
/* Remove standalone port entry */
|
|
|
|
sparx5_mact_forget(sparx5, ndev->dev_addr, 0);
|
|
|
|
|
2024-04-24 16:13:26 +01:00
|
|
|
/* Port enters in bridge mode therefore don't need to copy to CPU
|
2021-06-24 09:07:55 +02:00
|
|
|
* frames for multicast in case the bridge is not requesting them
|
|
|
|
*/
|
net: bridge: switchdev: let drivers inform which bridge ports are offloaded
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>
2021-07-21 19:24:01 +03:00
|
|
|
__dev_mc_unsync(ndev, sparx5_mc_unsync);
|
2021-06-24 09:07:55 +02:00
|
|
|
|
|
|
|
return 0;
|
net: bridge: switchdev: let drivers inform which bridge ports are offloaded
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>
2021-07-21 19:24:01 +03:00
|
|
|
|
|
|
|
err_switchdev_offload:
|
|
|
|
clear_bit(port->portno, sparx5->bridge_mask);
|
|
|
|
return err;
|
2021-06-24 09:07:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void sparx5_port_bridge_leave(struct sparx5_port *port,
|
|
|
|
struct net_device *bridge)
|
|
|
|
{
|
|
|
|
struct sparx5 *sparx5 = port->sparx5;
|
|
|
|
|
net: bridge: move the switchdev object replay helpers to "push" mode
Starting with commit 4f2673b3a2b6 ("net: bridge: add helper to replay
port and host-joined mdb entries"), DSA has introduced some bridge
helpers that replay switchdev events (FDB/MDB/VLAN additions and
deletions) that can be lost by the switchdev drivers in a variety of
circumstances:
- an IP multicast group was host-joined on the bridge itself before any
switchdev port joined the bridge, leading to the host MDB entries
missing in the hardware database.
- during the bridge creation process, the MAC address of the bridge was
added to the FDB as an entry pointing towards the bridge device
itself, but with no switchdev ports being part of the bridge yet, this
local FDB entry would remain unknown to the switchdev hardware
database.
- a VLAN/FDB/MDB was added to a bridge port that is a LAG interface,
before any switchdev port joined that LAG, leading to the hardware
database missing those entries.
- a switchdev port left a LAG that is a bridge port, while the LAG
remained part of the bridge, and all FDB/MDB/VLAN entries remained
installed in the hardware database of the switchdev port.
Also, since commit 0d2cfbd41c4a ("net: bridge: ignore switchdev events
for LAG ports which didn't request replay"), DSA introduced a method,
based on a const void *ctx, to ensure that two switchdev ports under the
same LAG that is a bridge port do not see the same MDB/VLAN entry being
replayed twice by the bridge, once for every bridge port that joins the
LAG.
With so many ordering corner cases being possible, it seems unreasonable
to expect a switchdev driver writer to get it right from the first try.
Therefore, now that DSA has experimented with the bridge replay helpers
for a little bit, we can move the code to the bridge driver where it is
more readily available to all switchdev drivers.
To convert the switchdev object replay helpers from "pull mode" (where
the driver asks for them) to a "push mode" (where the bridge offers them
automatically), the biggest problem is that the bridge needs to be aware
when a switchdev port joins and leaves, even when the switchdev is only
indirectly a bridge port (for example when the bridge port is a LAG
upper of the switchdev).
Luckily, we already have a hook for that, in the form of the newly
introduced switchdev_bridge_port_offload() and
switchdev_bridge_port_unoffload() calls. These offer a natural place for
hooking the object addition and deletion replays.
Extend the above 2 functions with:
- pointers to the switchdev atomic notifier (for FDB replays) and the
blocking notifier (for MDB and VLAN replays).
- the "const void *ctx" argument required for drivers to be able to
disambiguate between which port is targeted, when multiple ports are
lowers of the same LAG that is a bridge port. Most of the drivers pass
NULL to this argument, except the ones that support LAG offload and have
the proper context check already in place in the switchdev blocking
notifier handler.
Also unexport the replay helpers, since nobody except the bridge calls
them directly now.
Note that:
(a) we abuse the terminology slightly, because FDB entries are not
"switchdev objects", but we count them as objects nonetheless.
With no direct way to prove it, I think they are not modeled as
switchdev objects because those can only be installed by the bridge
to the hardware (as opposed to FDB entries which can be propagated
in the other direction too). This is merely an abuse of terms, FDB
entries are replayed too, despite not being objects.
(b) the bridge does not attempt to sync port attributes to newly joined
ports, just the countable stuff (the objects). The reason for this
is simple: no universal and symmetric way to sync and unsync them is
known. For example, VLAN filtering: what to do on unsync, disable or
leave it enabled? Similarly, STP state, ageing timer, etc etc. What
a switchdev port does when it becomes standalone again is not really
up to the bridge's competence, and the driver should deal with it.
On the other hand, replaying deletions of switchdev objects can be
seen a matter of cleanup and therefore be treated by the bridge,
hence this patch.
We make the replay helpers opt-in for drivers, because they might not
bring immediate benefits for them:
- nbp_vlan_init() is called _after_ netdev_master_upper_dev_link(),
so br_vlan_replay() should not do anything for the new drivers on
which we call it. The existing drivers where there was even a slight
possibility for there to exist a VLAN on a bridge port before they
join it are already guarded against this: mlxsw and prestera deny
joining LAG interfaces that are members of a bridge.
- br_fdb_replay() should now notify of local FDB entries, but I patched
all drivers except DSA to ignore these new entries in commit
2c4eca3ef716 ("net: bridge: switchdev: include local flag in FDB
notifications"). Driver authors can lift this restriction as they
wish, and when they do, they can also opt into the FDB replay
functionality.
- br_mdb_replay() should fix a real issue which is described in commit
4f2673b3a2b6 ("net: bridge: add helper to replay port and host-joined
mdb entries"). However most drivers do not offload the
SWITCHDEV_OBJ_ID_HOST_MDB to see this issue: only cpsw and am65_cpsw
offload this switchdev object, and I don't completely understand the
way in which they offload this switchdev object anyway. So I'll leave
it up to these drivers' respective maintainers to opt into
br_mdb_replay().
So most of the drivers pass NULL notifier blocks for the replay helpers,
except:
- dpaa2-switch which was already acked/regression-tested with the
helpers enabled (and there isn't much of a downside in having them)
- ocelot which already had replay logic in "pull" mode
- DSA which already had replay logic in "pull" mode
An important observation is that the drivers which don't currently
request bridge event replays don't even have the
switchdev_bridge_port_{offload,unoffload} calls placed in proper places
right now. This was done to avoid unnecessary rework for drivers which
might never even add support for this. For driver writers who wish to
add replay support, this can be used as a tentative placement guide:
https://patchwork.kernel.org/project/netdevbpf/patch/20210720134655.892334-11-vladimir.oltean@nxp.com/
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>
Acked-by: Ioana Ciornei <ioana.ciornei@nxp.com> # dpaa2-switch
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-07-21 19:24:03 +03:00
|
|
|
switchdev_bridge_port_unoffload(port->ndev, NULL, NULL, NULL);
|
net: bridge: switchdev: let drivers inform which bridge ports are offloaded
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>
2021-07-21 19:24:01 +03:00
|
|
|
|
2021-06-24 09:07:55 +02:00
|
|
|
clear_bit(port->portno, sparx5->bridge_mask);
|
|
|
|
if (bitmap_empty(sparx5->bridge_mask, SPX5_PORTS))
|
|
|
|
sparx5->hw_bridge_dev = NULL;
|
|
|
|
|
|
|
|
/* Clear bridge vlan settings before updating the port settings */
|
|
|
|
port->vlan_aware = 0;
|
|
|
|
port->pvid = NULL_VID;
|
|
|
|
port->vid = NULL_VID;
|
|
|
|
|
2022-03-18 13:53:31 +01:00
|
|
|
/* Forward frames to CPU */
|
|
|
|
sparx5_mact_learn(sparx5, PGID_CPU, port->ndev->dev_addr, 0);
|
|
|
|
|
2021-06-24 09:07:55 +02:00
|
|
|
/* Port enters in host more therefore restore mc list */
|
|
|
|
__dev_mc_sync(port->ndev, sparx5_mc_sync, sparx5_mc_unsync);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sparx5_port_changeupper(struct net_device *dev,
|
|
|
|
struct netdev_notifier_changeupper_info *info)
|
|
|
|
{
|
|
|
|
struct sparx5_port *port = netdev_priv(dev);
|
net: bridge: switchdev: let drivers inform which bridge ports are offloaded
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>
2021-07-21 19:24:01 +03:00
|
|
|
struct netlink_ext_ack *extack;
|
2021-06-24 09:07:55 +02:00
|
|
|
int err = 0;
|
|
|
|
|
net: bridge: switchdev: let drivers inform which bridge ports are offloaded
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>
2021-07-21 19:24:01 +03:00
|
|
|
extack = netdev_notifier_info_to_extack(&info->info);
|
|
|
|
|
2021-06-24 09:07:55 +02:00
|
|
|
if (netif_is_bridge_master(info->upper_dev)) {
|
|
|
|
if (info->linking)
|
net: bridge: switchdev: let drivers inform which bridge ports are offloaded
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>
2021-07-21 19:24:01 +03:00
|
|
|
err = sparx5_port_bridge_join(port, info->upper_dev,
|
|
|
|
extack);
|
2021-06-24 09:07:55 +02:00
|
|
|
else
|
|
|
|
sparx5_port_bridge_leave(port, info->upper_dev);
|
|
|
|
|
|
|
|
sparx5_vlan_port_apply(port->sparx5, port);
|
|
|
|
}
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sparx5_port_add_addr(struct net_device *dev, bool up)
|
|
|
|
{
|
|
|
|
struct sparx5_port *port = netdev_priv(dev);
|
|
|
|
struct sparx5 *sparx5 = port->sparx5;
|
|
|
|
u16 vid = port->pvid;
|
|
|
|
|
|
|
|
if (up)
|
|
|
|
sparx5_mact_learn(sparx5, PGID_CPU, port->ndev->dev_addr, vid);
|
|
|
|
else
|
|
|
|
sparx5_mact_forget(sparx5, port->ndev->dev_addr, vid);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sparx5_netdevice_port_event(struct net_device *dev,
|
|
|
|
struct notifier_block *nb,
|
|
|
|
unsigned long event, void *ptr)
|
|
|
|
{
|
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
if (!sparx5_netdevice_check(dev))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
switch (event) {
|
|
|
|
case NETDEV_CHANGEUPPER:
|
|
|
|
err = sparx5_port_changeupper(dev, ptr);
|
|
|
|
break;
|
|
|
|
case NETDEV_PRE_UP:
|
|
|
|
err = sparx5_port_add_addr(dev, true);
|
|
|
|
break;
|
|
|
|
case NETDEV_DOWN:
|
|
|
|
err = sparx5_port_add_addr(dev, false);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sparx5_netdevice_event(struct notifier_block *nb,
|
|
|
|
unsigned long event, void *ptr)
|
|
|
|
{
|
|
|
|
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
ret = sparx5_netdevice_port_event(dev, nb, event, ptr);
|
|
|
|
|
|
|
|
return notifier_from_errno(ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sparx5_switchdev_bridge_fdb_event_work(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct sparx5_switchdev_event_work *switchdev_work =
|
|
|
|
container_of(work, struct sparx5_switchdev_event_work, work);
|
|
|
|
struct net_device *dev = switchdev_work->dev;
|
|
|
|
struct switchdev_notifier_fdb_info *fdb_info;
|
|
|
|
struct sparx5_port *port;
|
|
|
|
struct sparx5 *sparx5;
|
2022-03-14 17:09:18 +01:00
|
|
|
bool host_addr;
|
2022-03-18 13:53:31 +01:00
|
|
|
u16 vid;
|
2021-06-24 09:07:55 +02:00
|
|
|
|
|
|
|
rtnl_lock();
|
2022-03-14 17:09:18 +01:00
|
|
|
if (!sparx5_netdevice_check(dev)) {
|
|
|
|
host_addr = true;
|
|
|
|
sparx5 = switchdev_work->sparx5;
|
|
|
|
} else {
|
|
|
|
host_addr = false;
|
|
|
|
sparx5 = switchdev_work->sparx5;
|
|
|
|
port = netdev_priv(dev);
|
|
|
|
}
|
2021-06-24 09:07:55 +02:00
|
|
|
|
|
|
|
fdb_info = &switchdev_work->fdb_info;
|
|
|
|
|
2022-03-18 13:53:31 +01:00
|
|
|
/* Used PVID 1 when default_pvid is 0, to avoid
|
|
|
|
* collision with non-bridged ports.
|
|
|
|
*/
|
|
|
|
if (fdb_info->vid == 0)
|
|
|
|
vid = 1;
|
|
|
|
else
|
|
|
|
vid = fdb_info->vid;
|
|
|
|
|
2021-06-24 09:07:55 +02:00
|
|
|
switch (switchdev_work->event) {
|
|
|
|
case SWITCHDEV_FDB_ADD_TO_DEVICE:
|
2022-03-14 17:09:18 +01:00
|
|
|
if (host_addr)
|
|
|
|
sparx5_add_mact_entry(sparx5, dev, PGID_CPU,
|
2022-03-18 13:53:31 +01:00
|
|
|
fdb_info->addr, vid);
|
2022-03-14 17:09:18 +01:00
|
|
|
else
|
|
|
|
sparx5_add_mact_entry(sparx5, port->ndev, port->portno,
|
2022-03-18 13:53:31 +01:00
|
|
|
fdb_info->addr, vid);
|
2021-06-24 09:07:55 +02:00
|
|
|
break;
|
|
|
|
case SWITCHDEV_FDB_DEL_TO_DEVICE:
|
2022-03-18 13:53:31 +01:00
|
|
|
sparx5_del_mact_entry(sparx5, fdb_info->addr, vid);
|
2021-06-24 09:07:55 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
rtnl_unlock();
|
|
|
|
kfree(switchdev_work->fdb_info.addr);
|
|
|
|
kfree(switchdev_work);
|
|
|
|
dev_put(dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sparx5_schedule_work(struct work_struct *work)
|
|
|
|
{
|
|
|
|
queue_work(sparx5_owq, work);
|
|
|
|
}
|
|
|
|
|
2022-03-14 17:09:18 +01:00
|
|
|
static int sparx5_switchdev_event(struct notifier_block *nb,
|
2021-06-24 09:07:55 +02:00
|
|
|
unsigned long event, void *ptr)
|
|
|
|
{
|
|
|
|
struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
|
|
|
|
struct sparx5_switchdev_event_work *switchdev_work;
|
|
|
|
struct switchdev_notifier_fdb_info *fdb_info;
|
|
|
|
struct switchdev_notifier_info *info = ptr;
|
2022-03-14 17:09:18 +01:00
|
|
|
struct sparx5 *spx5;
|
2021-06-24 09:07:55 +02:00
|
|
|
int err;
|
|
|
|
|
2022-03-14 17:09:18 +01:00
|
|
|
spx5 = container_of(nb, struct sparx5, switchdev_nb);
|
|
|
|
|
2021-06-24 09:07:55 +02:00
|
|
|
switch (event) {
|
|
|
|
case SWITCHDEV_PORT_ATTR_SET:
|
|
|
|
err = switchdev_handle_port_attr_set(dev, ptr,
|
|
|
|
sparx5_netdevice_check,
|
|
|
|
sparx5_port_attr_set);
|
|
|
|
return notifier_from_errno(err);
|
|
|
|
case SWITCHDEV_FDB_ADD_TO_DEVICE:
|
|
|
|
fallthrough;
|
|
|
|
case SWITCHDEV_FDB_DEL_TO_DEVICE:
|
|
|
|
switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC);
|
|
|
|
if (!switchdev_work)
|
|
|
|
return NOTIFY_BAD;
|
|
|
|
|
|
|
|
switchdev_work->dev = dev;
|
|
|
|
switchdev_work->event = event;
|
2022-03-14 17:09:18 +01:00
|
|
|
switchdev_work->sparx5 = spx5;
|
2021-06-24 09:07:55 +02:00
|
|
|
|
|
|
|
fdb_info = container_of(info,
|
|
|
|
struct switchdev_notifier_fdb_info,
|
|
|
|
info);
|
|
|
|
INIT_WORK(&switchdev_work->work,
|
|
|
|
sparx5_switchdev_bridge_fdb_event_work);
|
|
|
|
memcpy(&switchdev_work->fdb_info, ptr,
|
|
|
|
sizeof(switchdev_work->fdb_info));
|
|
|
|
switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC);
|
|
|
|
if (!switchdev_work->fdb_info.addr)
|
|
|
|
goto err_addr_alloc;
|
|
|
|
|
|
|
|
ether_addr_copy((u8 *)switchdev_work->fdb_info.addr,
|
|
|
|
fdb_info->addr);
|
|
|
|
dev_hold(dev);
|
|
|
|
|
|
|
|
sparx5_schedule_work(&switchdev_work->work);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NOTIFY_DONE;
|
|
|
|
err_addr_alloc:
|
|
|
|
kfree(switchdev_work);
|
|
|
|
return NOTIFY_BAD;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sparx5_handle_port_vlan_add(struct net_device *dev,
|
|
|
|
struct notifier_block *nb,
|
|
|
|
const struct switchdev_obj_port_vlan *v)
|
|
|
|
{
|
|
|
|
struct sparx5_port *port = netdev_priv(dev);
|
|
|
|
|
|
|
|
if (netif_is_bridge_master(dev)) {
|
2022-02-16 18:47:50 +02:00
|
|
|
struct sparx5 *sparx5 =
|
|
|
|
container_of(nb, struct sparx5,
|
|
|
|
switchdev_blocking_nb);
|
2021-06-24 09:07:55 +02:00
|
|
|
|
2022-03-14 17:09:18 +01:00
|
|
|
/* Flood broadcast to CPU */
|
|
|
|
sparx5_mact_learn(sparx5, PGID_BCAST, dev->broadcast,
|
|
|
|
v->vid);
|
2021-06-24 09:07:55 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!sparx5_netdevice_check(dev))
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
|
|
|
return sparx5_vlan_vid_add(port, v->vid,
|
|
|
|
v->flags & BRIDGE_VLAN_INFO_PVID,
|
|
|
|
v->flags & BRIDGE_VLAN_INFO_UNTAGGED);
|
|
|
|
}
|
|
|
|
|
2022-08-25 11:28:36 +02:00
|
|
|
static int sparx5_alloc_mdb_entry(struct sparx5 *sparx5,
|
|
|
|
const unsigned char *addr,
|
|
|
|
u16 vid,
|
|
|
|
struct sparx5_mdb_entry **entry_out)
|
|
|
|
{
|
|
|
|
struct sparx5_mdb_entry *entry;
|
|
|
|
u16 pgid_idx;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
|
|
|
|
if (!entry)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
err = sparx5_pgid_alloc_mcast(sparx5, &pgid_idx);
|
|
|
|
if (err) {
|
|
|
|
kfree(entry);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(entry->addr, addr, ETH_ALEN);
|
|
|
|
entry->vid = vid;
|
|
|
|
entry->pgid_idx = pgid_idx;
|
|
|
|
|
|
|
|
mutex_lock(&sparx5->mdb_lock);
|
|
|
|
list_add_tail(&entry->list, &sparx5->mdb_entries);
|
|
|
|
mutex_unlock(&sparx5->mdb_lock);
|
|
|
|
|
|
|
|
*entry_out = entry;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sparx5_free_mdb_entry(struct sparx5 *sparx5,
|
|
|
|
const unsigned char *addr,
|
|
|
|
u16 vid)
|
|
|
|
{
|
|
|
|
struct sparx5_mdb_entry *entry, *tmp;
|
|
|
|
|
|
|
|
mutex_lock(&sparx5->mdb_lock);
|
|
|
|
list_for_each_entry_safe(entry, tmp, &sparx5->mdb_entries, list) {
|
|
|
|
if ((vid == 0 || entry->vid == vid) &&
|
|
|
|
ether_addr_equal(addr, entry->addr)) {
|
|
|
|
list_del(&entry->list);
|
|
|
|
|
|
|
|
sparx5_pgid_free(sparx5, entry->pgid_idx);
|
|
|
|
kfree(entry);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
|
|
|
mutex_unlock(&sparx5->mdb_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct sparx5_mdb_entry *sparx5_mdb_get_entry(struct sparx5 *sparx5,
|
|
|
|
const unsigned char *addr,
|
|
|
|
u16 vid)
|
|
|
|
{
|
|
|
|
struct sparx5_mdb_entry *e, *found = NULL;
|
|
|
|
|
|
|
|
mutex_lock(&sparx5->mdb_lock);
|
|
|
|
list_for_each_entry(e, &sparx5->mdb_entries, list) {
|
|
|
|
if (ether_addr_equal(e->addr, addr) && e->vid == vid) {
|
|
|
|
found = e;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
|
|
|
mutex_unlock(&sparx5->mdb_lock);
|
|
|
|
return found;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sparx5_cpu_copy_ena(struct sparx5 *spx5, u16 pgid, bool enable)
|
|
|
|
{
|
|
|
|
spx5_rmw(ANA_AC_PGID_MISC_CFG_PGID_CPU_COPY_ENA_SET(enable),
|
|
|
|
ANA_AC_PGID_MISC_CFG_PGID_CPU_COPY_ENA, spx5,
|
|
|
|
ANA_AC_PGID_MISC_CFG(pgid));
|
|
|
|
}
|
|
|
|
|
2022-03-21 11:14:46 +01:00
|
|
|
static int sparx5_handle_port_mdb_add(struct net_device *dev,
|
|
|
|
struct notifier_block *nb,
|
|
|
|
const struct switchdev_obj_port_mdb *v)
|
|
|
|
{
|
|
|
|
struct sparx5_port *port = netdev_priv(dev);
|
|
|
|
struct sparx5 *spx5 = port->sparx5;
|
2022-08-25 11:28:36 +02:00
|
|
|
struct sparx5_mdb_entry *entry;
|
2022-08-25 11:28:37 +02:00
|
|
|
bool is_host, is_new;
|
|
|
|
int err, i;
|
2022-08-25 11:28:36 +02:00
|
|
|
u16 vid;
|
2022-03-21 11:14:46 +01:00
|
|
|
|
2022-06-30 14:22:26 +02:00
|
|
|
if (!sparx5_netdevice_check(dev))
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
2022-06-14 11:25:32 +02:00
|
|
|
is_host = netif_is_bridge_master(v->obj.orig_dev);
|
2022-05-03 11:39:22 +02:00
|
|
|
|
2022-03-21 11:14:46 +01:00
|
|
|
/* When VLAN unaware the vlan value is not parsed and we receive vid 0.
|
|
|
|
* Fall back to bridge vid 1.
|
|
|
|
*/
|
|
|
|
if (!br_vlan_enabled(spx5->hw_bridge_dev))
|
|
|
|
vid = 1;
|
|
|
|
else
|
|
|
|
vid = v->vid;
|
|
|
|
|
2022-08-25 11:28:37 +02:00
|
|
|
is_new = false;
|
2022-08-25 11:28:36 +02:00
|
|
|
entry = sparx5_mdb_get_entry(spx5, v->addr, vid);
|
|
|
|
if (!entry) {
|
|
|
|
err = sparx5_alloc_mdb_entry(spx5, v->addr, vid, &entry);
|
2022-08-25 11:28:37 +02:00
|
|
|
is_new = true;
|
2022-08-25 11:28:36 +02:00
|
|
|
if (err)
|
2022-03-21 11:14:46 +01:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2022-08-25 11:28:36 +02:00
|
|
|
mutex_lock(&spx5->mdb_lock);
|
2022-08-25 11:28:37 +02:00
|
|
|
|
|
|
|
/* Add any mrouter ports to the new entry */
|
|
|
|
if (is_new && ether_addr_is_ip_mcast(v->addr))
|
|
|
|
for (i = 0; i < SPX5_PORTS; i++)
|
|
|
|
if (spx5->ports[i] && spx5->ports[i]->is_mrouter)
|
|
|
|
sparx5_pgid_update_mask(spx5->ports[i],
|
|
|
|
entry->pgid_idx,
|
|
|
|
true);
|
|
|
|
|
2022-08-25 11:28:36 +02:00
|
|
|
if (is_host && !entry->cpu_copy) {
|
|
|
|
sparx5_cpu_copy_ena(spx5, entry->pgid_idx, true);
|
|
|
|
entry->cpu_copy = true;
|
|
|
|
} else if (!is_host) {
|
|
|
|
sparx5_pgid_update_mask(port, entry->pgid_idx, true);
|
|
|
|
set_bit(port->portno, entry->port_mask);
|
|
|
|
}
|
|
|
|
mutex_unlock(&spx5->mdb_lock);
|
2022-03-21 11:14:46 +01:00
|
|
|
|
2022-08-25 11:28:36 +02:00
|
|
|
sparx5_mact_learn(spx5, entry->pgid_idx, entry->addr, entry->vid);
|
2022-03-21 11:14:46 +01:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sparx5_handle_port_mdb_del(struct net_device *dev,
|
|
|
|
struct notifier_block *nb,
|
|
|
|
const struct switchdev_obj_port_mdb *v)
|
|
|
|
{
|
|
|
|
struct sparx5_port *port = netdev_priv(dev);
|
|
|
|
struct sparx5 *spx5 = port->sparx5;
|
2022-08-25 11:28:36 +02:00
|
|
|
struct sparx5_mdb_entry *entry;
|
|
|
|
bool is_host;
|
|
|
|
u16 vid;
|
2022-05-03 11:39:22 +02:00
|
|
|
|
2022-06-30 14:22:26 +02:00
|
|
|
if (!sparx5_netdevice_check(dev))
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
2022-08-25 11:28:36 +02:00
|
|
|
is_host = netif_is_bridge_master(v->obj.orig_dev);
|
|
|
|
|
2022-03-21 11:14:46 +01:00
|
|
|
if (!br_vlan_enabled(spx5->hw_bridge_dev))
|
|
|
|
vid = 1;
|
|
|
|
else
|
|
|
|
vid = v->vid;
|
|
|
|
|
2022-08-25 11:28:36 +02:00
|
|
|
entry = sparx5_mdb_get_entry(spx5, v->addr, vid);
|
|
|
|
if (!entry)
|
|
|
|
return 0;
|
2022-03-21 11:14:46 +01:00
|
|
|
|
2022-08-25 11:28:36 +02:00
|
|
|
mutex_lock(&spx5->mdb_lock);
|
|
|
|
if (is_host && entry->cpu_copy) {
|
|
|
|
sparx5_cpu_copy_ena(spx5, entry->pgid_idx, false);
|
|
|
|
entry->cpu_copy = false;
|
|
|
|
} else if (!is_host) {
|
|
|
|
clear_bit(port->portno, entry->port_mask);
|
2022-08-25 11:28:37 +02:00
|
|
|
|
|
|
|
/* Port not mrouter port or addr is L2 mcast, remove port from mask. */
|
|
|
|
if (!port->is_mrouter || !ether_addr_is_ip_mcast(v->addr))
|
|
|
|
sparx5_pgid_update_mask(port, entry->pgid_idx, false);
|
2022-03-21 11:14:46 +01:00
|
|
|
}
|
2022-08-25 11:28:36 +02:00
|
|
|
mutex_unlock(&spx5->mdb_lock);
|
2022-03-21 11:14:46 +01:00
|
|
|
|
2022-08-25 11:28:36 +02:00
|
|
|
if (bitmap_empty(entry->port_mask, SPX5_PORTS) && !entry->cpu_copy) {
|
2022-08-25 11:28:37 +02:00
|
|
|
/* Clear pgid in case mrouter ports exists
|
|
|
|
* that are not part of the group.
|
|
|
|
*/
|
|
|
|
sparx5_pgid_clear(spx5, entry->pgid_idx);
|
2022-08-25 11:28:36 +02:00
|
|
|
sparx5_mact_forget(spx5, entry->addr, entry->vid);
|
|
|
|
sparx5_free_mdb_entry(spx5, entry->addr, entry->vid);
|
|
|
|
}
|
2022-03-21 11:14:46 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-06-24 09:07:55 +02:00
|
|
|
static int sparx5_handle_port_obj_add(struct net_device *dev,
|
|
|
|
struct notifier_block *nb,
|
|
|
|
struct switchdev_notifier_port_obj_info *info)
|
|
|
|
{
|
|
|
|
const struct switchdev_obj *obj = info->obj;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
switch (obj->id) {
|
|
|
|
case SWITCHDEV_OBJ_ID_PORT_VLAN:
|
|
|
|
err = sparx5_handle_port_vlan_add(dev, nb,
|
|
|
|
SWITCHDEV_OBJ_PORT_VLAN(obj));
|
|
|
|
break;
|
2022-03-21 11:14:46 +01:00
|
|
|
case SWITCHDEV_OBJ_ID_PORT_MDB:
|
2022-05-03 11:39:22 +02:00
|
|
|
case SWITCHDEV_OBJ_ID_HOST_MDB:
|
2022-03-21 11:14:46 +01:00
|
|
|
err = sparx5_handle_port_mdb_add(dev, nb,
|
|
|
|
SWITCHDEV_OBJ_PORT_MDB(obj));
|
|
|
|
break;
|
2021-06-24 09:07:55 +02:00
|
|
|
default:
|
|
|
|
err = -EOPNOTSUPP;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
info->handled = true;
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sparx5_handle_port_vlan_del(struct net_device *dev,
|
|
|
|
struct notifier_block *nb,
|
|
|
|
u16 vid)
|
|
|
|
{
|
|
|
|
struct sparx5_port *port = netdev_priv(dev);
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
/* Master bridge? */
|
|
|
|
if (netif_is_bridge_master(dev)) {
|
|
|
|
struct sparx5 *sparx5 =
|
|
|
|
container_of(nb, struct sparx5,
|
|
|
|
switchdev_blocking_nb);
|
|
|
|
|
2022-03-14 17:09:18 +01:00
|
|
|
sparx5_mact_forget(sparx5, dev->broadcast, vid);
|
2021-06-24 09:07:55 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!sparx5_netdevice_check(dev))
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
|
|
|
ret = sparx5_vlan_vid_del(port, vid);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sparx5_handle_port_obj_del(struct net_device *dev,
|
|
|
|
struct notifier_block *nb,
|
|
|
|
struct switchdev_notifier_port_obj_info *info)
|
|
|
|
{
|
|
|
|
const struct switchdev_obj *obj = info->obj;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
switch (obj->id) {
|
|
|
|
case SWITCHDEV_OBJ_ID_PORT_VLAN:
|
|
|
|
err = sparx5_handle_port_vlan_del(dev, nb,
|
|
|
|
SWITCHDEV_OBJ_PORT_VLAN(obj)->vid);
|
|
|
|
break;
|
2022-03-21 11:14:46 +01:00
|
|
|
case SWITCHDEV_OBJ_ID_PORT_MDB:
|
2022-05-03 11:39:22 +02:00
|
|
|
case SWITCHDEV_OBJ_ID_HOST_MDB:
|
2022-03-21 11:14:46 +01:00
|
|
|
err = sparx5_handle_port_mdb_del(dev, nb,
|
|
|
|
SWITCHDEV_OBJ_PORT_MDB(obj));
|
|
|
|
break;
|
2021-06-24 09:07:55 +02:00
|
|
|
default:
|
|
|
|
err = -EOPNOTSUPP;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
info->handled = true;
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sparx5_switchdev_blocking_event(struct notifier_block *nb,
|
|
|
|
unsigned long event,
|
|
|
|
void *ptr)
|
|
|
|
{
|
|
|
|
struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
|
|
|
|
int err;
|
|
|
|
|
|
|
|
switch (event) {
|
|
|
|
case SWITCHDEV_PORT_OBJ_ADD:
|
|
|
|
err = sparx5_handle_port_obj_add(dev, nb, ptr);
|
|
|
|
return notifier_from_errno(err);
|
|
|
|
case SWITCHDEV_PORT_OBJ_DEL:
|
|
|
|
err = sparx5_handle_port_obj_del(dev, nb, ptr);
|
|
|
|
return notifier_from_errno(err);
|
|
|
|
case SWITCHDEV_PORT_ATTR_SET:
|
|
|
|
err = switchdev_handle_port_attr_set(dev, ptr,
|
|
|
|
sparx5_netdevice_check,
|
|
|
|
sparx5_port_attr_set);
|
|
|
|
return notifier_from_errno(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
return NOTIFY_DONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
int sparx5_register_notifier_blocks(struct sparx5 *s5)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
|
|
|
s5->netdevice_nb.notifier_call = sparx5_netdevice_event;
|
|
|
|
err = register_netdevice_notifier(&s5->netdevice_nb);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
s5->switchdev_nb.notifier_call = sparx5_switchdev_event;
|
|
|
|
err = register_switchdev_notifier(&s5->switchdev_nb);
|
|
|
|
if (err)
|
|
|
|
goto err_switchdev_nb;
|
|
|
|
|
|
|
|
s5->switchdev_blocking_nb.notifier_call = sparx5_switchdev_blocking_event;
|
|
|
|
err = register_switchdev_blocking_notifier(&s5->switchdev_blocking_nb);
|
|
|
|
if (err)
|
|
|
|
goto err_switchdev_blocking_nb;
|
|
|
|
|
|
|
|
sparx5_owq = alloc_ordered_workqueue("sparx5_order", 0);
|
2021-06-26 12:44:20 +08:00
|
|
|
if (!sparx5_owq) {
|
|
|
|
err = -ENOMEM;
|
2021-06-24 09:07:55 +02:00
|
|
|
goto err_switchdev_blocking_nb;
|
2021-06-26 12:44:20 +08:00
|
|
|
}
|
2021-06-24 09:07:55 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_switchdev_blocking_nb:
|
|
|
|
unregister_switchdev_notifier(&s5->switchdev_nb);
|
|
|
|
err_switchdev_nb:
|
|
|
|
unregister_netdevice_notifier(&s5->netdevice_nb);
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
void sparx5_unregister_notifier_blocks(struct sparx5 *s5)
|
|
|
|
{
|
|
|
|
destroy_workqueue(sparx5_owq);
|
|
|
|
|
|
|
|
unregister_switchdev_blocking_notifier(&s5->switchdev_blocking_nb);
|
|
|
|
unregister_switchdev_notifier(&s5->switchdev_nb);
|
|
|
|
unregister_netdevice_notifier(&s5->netdevice_nb);
|
|
|
|
}
|