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

net: dsa: use switchdev_handle_fdb_{add,del}_to_device

Using the new fan-out helper for FDB entries installed on the software
bridge, we can install host addresses with the proper refcount on the
CPU port, such that this case:

ip link set swp0 master br0
ip link set swp1 master br0
ip link set swp2 master br0
ip link set swp3 master br0
ip link set br0 address 00:01:02:03:04:05
ip link set swp3 nomaster

works properly and the br0 address remains installed as a host entry
with refcount 3 instead of getting deleted.
Signed-off-by: default avatarVladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 8ca07176
...@@ -268,13 +268,13 @@ void dsa_port_tag_8021q_vlan_del(struct dsa_port *dp, u16 vid); ...@@ -268,13 +268,13 @@ void dsa_port_tag_8021q_vlan_del(struct dsa_port *dp, u16 vid);
extern const struct phylink_mac_ops dsa_port_phylink_mac_ops; extern const struct phylink_mac_ops dsa_port_phylink_mac_ops;
static inline bool dsa_port_offloads_bridge_port(struct dsa_port *dp, static inline bool dsa_port_offloads_bridge_port(struct dsa_port *dp,
struct net_device *dev) const struct net_device *dev)
{ {
return dsa_port_to_bridge_port(dp) == dev; return dsa_port_to_bridge_port(dp) == dev;
} }
static inline bool dsa_port_offloads_bridge(struct dsa_port *dp, static inline bool dsa_port_offloads_bridge(struct dsa_port *dp,
struct net_device *bridge_dev) const struct net_device *bridge_dev)
{ {
/* DSA ports connected to a bridge, and event was emitted /* DSA ports connected to a bridge, and event was emitted
* for the bridge. * for the bridge.
...@@ -284,7 +284,7 @@ static inline bool dsa_port_offloads_bridge(struct dsa_port *dp, ...@@ -284,7 +284,7 @@ static inline bool dsa_port_offloads_bridge(struct dsa_port *dp,
/* Returns true if any port of this tree offloads the given net_device */ /* Returns true if any port of this tree offloads the given net_device */
static inline bool dsa_tree_offloads_bridge_port(struct dsa_switch_tree *dst, static inline bool dsa_tree_offloads_bridge_port(struct dsa_switch_tree *dst,
struct net_device *dev) const struct net_device *dev)
{ {
struct dsa_port *dp; struct dsa_port *dp;
...@@ -295,6 +295,19 @@ static inline bool dsa_tree_offloads_bridge_port(struct dsa_switch_tree *dst, ...@@ -295,6 +295,19 @@ static inline bool dsa_tree_offloads_bridge_port(struct dsa_switch_tree *dst,
return false; return false;
} }
/* Returns true if any port of this tree offloads the given bridge */
static inline bool dsa_tree_offloads_bridge(struct dsa_switch_tree *dst,
const struct net_device *bridge_dev)
{
struct dsa_port *dp;
list_for_each_entry(dp, &dst->ports, list)
if (dsa_port_offloads_bridge(dp, bridge_dev))
return true;
return false;
}
/* slave.c */ /* slave.c */
extern const struct dsa_device_ops notag_netdev_ops; extern const struct dsa_device_ops notag_netdev_ops;
extern struct notifier_block dsa_slave_switchdev_notifier; extern struct notifier_block dsa_slave_switchdev_notifier;
......
...@@ -2353,26 +2353,98 @@ static void dsa_slave_switchdev_event_work(struct work_struct *work) ...@@ -2353,26 +2353,98 @@ static void dsa_slave_switchdev_event_work(struct work_struct *work)
kfree(switchdev_work); kfree(switchdev_work);
} }
static int dsa_lower_dev_walk(struct net_device *lower_dev, static bool dsa_foreign_dev_check(const struct net_device *dev,
struct netdev_nested_priv *priv) const struct net_device *foreign_dev)
{ {
if (dsa_slave_dev_check(lower_dev)) { const struct dsa_port *dp = dsa_slave_to_port(dev);
priv->data = (void *)netdev_priv(lower_dev); struct dsa_switch_tree *dst = dp->ds->dst;
return 1;
}
return 0; if (netif_is_bridge_master(foreign_dev))
return !dsa_tree_offloads_bridge(dst, foreign_dev);
if (netif_is_bridge_port(foreign_dev))
return !dsa_tree_offloads_bridge_port(dst, foreign_dev);
/* Everything else is foreign */
return true;
} }
static struct dsa_slave_priv *dsa_slave_dev_lower_find(struct net_device *dev) static int dsa_slave_fdb_event(struct net_device *dev,
const struct net_device *orig_dev,
const void *ctx,
const struct switchdev_notifier_fdb_info *fdb_info,
unsigned long event)
{ {
struct netdev_nested_priv priv = { struct dsa_switchdev_event_work *switchdev_work;
.data = NULL, struct dsa_port *dp = dsa_slave_to_port(dev);
}; bool host_addr = fdb_info->is_local;
struct dsa_switch *ds = dp->ds;
if (ctx && ctx != dp)
return 0;
if (!ds->ops->port_fdb_add || !ds->ops->port_fdb_del)
return -EOPNOTSUPP;
if (dsa_slave_dev_check(orig_dev) &&
switchdev_fdb_is_dynamically_learned(fdb_info))
return 0;
/* FDB entries learned by the software bridge should be installed as
* host addresses only if the driver requests assisted learning.
*/
if (switchdev_fdb_is_dynamically_learned(fdb_info) &&
!ds->assisted_learning_on_cpu_port)
return 0;
/* Also treat FDB entries on foreign interfaces bridged with us as host
* addresses.
*/
if (dsa_foreign_dev_check(dev, orig_dev))
host_addr = true;
switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC);
if (!switchdev_work)
return -ENOMEM;
netdev_dbg(dev, "%s FDB entry towards %s, addr %pM vid %d%s\n",
event == SWITCHDEV_FDB_ADD_TO_DEVICE ? "Adding" : "Deleting",
orig_dev->name, fdb_info->addr, fdb_info->vid,
host_addr ? " as host address" : "");
netdev_walk_all_lower_dev_rcu(dev, dsa_lower_dev_walk, &priv); INIT_WORK(&switchdev_work->work, dsa_slave_switchdev_event_work);
switchdev_work->ds = ds;
switchdev_work->port = dp->index;
switchdev_work->event = event;
switchdev_work->dev = dev;
return (struct dsa_slave_priv *)priv.data; ether_addr_copy(switchdev_work->addr, fdb_info->addr);
switchdev_work->vid = fdb_info->vid;
switchdev_work->host_addr = host_addr;
/* Hold a reference for dsa_fdb_offload_notify */
dev_hold(dev);
dsa_schedule_work(&switchdev_work->work);
return 0;
}
static int
dsa_slave_fdb_add_to_device(struct net_device *dev,
const struct net_device *orig_dev, const void *ctx,
const struct switchdev_notifier_fdb_info *fdb_info)
{
return dsa_slave_fdb_event(dev, orig_dev, ctx, fdb_info,
SWITCHDEV_FDB_ADD_TO_DEVICE);
}
static int
dsa_slave_fdb_del_to_device(struct net_device *dev,
const struct net_device *orig_dev, const void *ctx,
const struct switchdev_notifier_fdb_info *fdb_info)
{
return dsa_slave_fdb_event(dev, orig_dev, ctx, fdb_info,
SWITCHDEV_FDB_DEL_TO_DEVICE);
} }
/* Called under rcu_read_lock() */ /* Called under rcu_read_lock() */
...@@ -2380,10 +2452,6 @@ static int dsa_slave_switchdev_event(struct notifier_block *unused, ...@@ -2380,10 +2452,6 @@ static int dsa_slave_switchdev_event(struct notifier_block *unused,
unsigned long event, void *ptr) unsigned long event, void *ptr)
{ {
struct net_device *dev = switchdev_notifier_info_to_dev(ptr); struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
const struct switchdev_notifier_fdb_info *fdb_info;
struct dsa_switchdev_event_work *switchdev_work;
bool host_addr = false;
struct dsa_port *dp;
int err; int err;
switch (event) { switch (event) {
...@@ -2393,92 +2461,19 @@ static int dsa_slave_switchdev_event(struct notifier_block *unused, ...@@ -2393,92 +2461,19 @@ static int dsa_slave_switchdev_event(struct notifier_block *unused,
dsa_slave_port_attr_set); dsa_slave_port_attr_set);
return notifier_from_errno(err); return notifier_from_errno(err);
case SWITCHDEV_FDB_ADD_TO_DEVICE: case SWITCHDEV_FDB_ADD_TO_DEVICE:
err = switchdev_handle_fdb_add_to_device(dev, ptr,
dsa_slave_dev_check,
dsa_foreign_dev_check,
dsa_slave_fdb_add_to_device,
NULL);
return notifier_from_errno(err);
case SWITCHDEV_FDB_DEL_TO_DEVICE: case SWITCHDEV_FDB_DEL_TO_DEVICE:
fdb_info = ptr; err = switchdev_handle_fdb_del_to_device(dev, ptr,
dsa_slave_dev_check,
if (dsa_slave_dev_check(dev)) { dsa_foreign_dev_check,
dp = dsa_slave_to_port(dev); dsa_slave_fdb_del_to_device,
NULL);
if (fdb_info->is_local) return notifier_from_errno(err);
host_addr = true;
else if (!fdb_info->added_by_user)
return NOTIFY_OK;
} else {
/* Snoop addresses added to foreign interfaces
* bridged with us, or the bridge
* itself. Dynamically learned addresses can
* also be added for switches that don't
* automatically learn SA from CPU-injected
* traffic.
*/
struct net_device *br_dev;
struct dsa_slave_priv *p;
if (netif_is_bridge_master(dev))
br_dev = dev;
else
br_dev = netdev_master_upper_dev_get_rcu(dev);
if (!br_dev)
return NOTIFY_DONE;
if (!netif_is_bridge_master(br_dev))
return NOTIFY_DONE;
p = dsa_slave_dev_lower_find(br_dev);
if (!p)
return NOTIFY_DONE;
dp = p->dp;
host_addr = fdb_info->is_local;
/* FDB entries learned by the software bridge should
* be installed as host addresses only if the driver
* requests assisted learning.
* On the other hand, FDB entries for local termination
* should always be installed.
*/
if (switchdev_fdb_is_dynamically_learned(fdb_info) &&
!dp->ds->assisted_learning_on_cpu_port)
return NOTIFY_DONE;
/* When the bridge learns an address on an offloaded
* LAG we don't want to send traffic to the CPU, the
* other ports bridged with the LAG should be able to
* autonomously forward towards it.
* On the other hand, if the address is local
* (therefore not learned) then we want to trap it to
* the CPU regardless of whether the interface it
* belongs to is offloaded or not.
*/
if (dsa_tree_offloads_bridge_port(dp->ds->dst, dev) &&
!fdb_info->is_local)
return NOTIFY_DONE;
}
if (!dp->ds->ops->port_fdb_add || !dp->ds->ops->port_fdb_del)
return NOTIFY_DONE;
switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC);
if (!switchdev_work)
return NOTIFY_BAD;
INIT_WORK(&switchdev_work->work,
dsa_slave_switchdev_event_work);
switchdev_work->ds = dp->ds;
switchdev_work->port = dp->index;
switchdev_work->event = event;
switchdev_work->dev = dev;
ether_addr_copy(switchdev_work->addr,
fdb_info->addr);
switchdev_work->vid = fdb_info->vid;
switchdev_work->host_addr = host_addr;
/* Hold a reference for dsa_fdb_offload_notify */
dev_hold(dev);
dsa_schedule_work(&switchdev_work->work);
break;
default: default:
return NOTIFY_DONE; return NOTIFY_DONE;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment