Commit 9172d2a0 authored by David S. Miller's avatar David S. Miller

Merge branch 'dsa-add-fabric-notifier'

Vivien Didelot says:

====================
net: dsa: add fabric notifier

When a switch fabric is composed of multiple switch chips, these chips
must be programmed accordingly when an event occurred on one of them.

Examples of such event include hardware bridging: when a Linux bridge
spans interconnected chips, they must be programmed to allow external
ports to ingress frames on their internal ports.

Another example is cross-chip hardware VLANs. Switch chips in-between
interconnected bridge ports must also configure a given VLAN to allow
packets to pass through them.

In order to support that, this patchset introduces a non-intrusive
notifier mechanism. It adds a notifier head in every DSA switch tree
(the said fabric), and a notifier block in every DSA switch chip.

When an even occurs, it is chained to all notifiers of the fabric.
Switch chips can react accordingly if they are cross-chip capable.

On a dynamic debug enabled system, bridging a port in a multi-chip
fabric will print something like this (ZII Rev B board):

    # brctl addif br0 lan3
    mv88e6085 0.1:00: crosschip DSA port 1.0 bridged to br0
    mv88e6085 0.4:00: crosschip DSA port 1.0 bridged to br0
    # brctl delif br0 lan3
    mv88e6085 0.1:00: crosschip DSA port 1.0 unbridged from br0
    mv88e6085 0.4:00: crosschip DSA port 1.0 unbridged from br0

Currently only bridging events are added. A patchset introducing support
for cross-chip hardware bridging configuration in mv88e6xxx will follow
right after. Then events for switchdev operations are next on the line.

We should note that non-switchdev events do not support rolling-back
switch-wide operations. We'll have to work on closer integration with
switchdev for that, like introducing new attributes or objects, to
benefit from the prepare and commit phases.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 321fa4ff 04d3a4c6
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include <linux/if_ether.h> #include <linux/if_ether.h>
#include <linux/list.h> #include <linux/list.h>
#include <linux/notifier.h>
#include <linux/timer.h> #include <linux/timer.h>
#include <linux/workqueue.h> #include <linux/workqueue.h>
#include <linux/of.h> #include <linux/of.h>
...@@ -92,6 +93,9 @@ struct packet_type; ...@@ -92,6 +93,9 @@ struct packet_type;
struct dsa_switch_tree { struct dsa_switch_tree {
struct list_head list; struct list_head list;
/* Notifier chain for switch-wide events */
struct raw_notifier_head nh;
/* Tree identifier */ /* Tree identifier */
u32 tree; u32 tree;
...@@ -182,6 +186,9 @@ struct dsa_switch { ...@@ -182,6 +186,9 @@ struct dsa_switch {
struct dsa_switch_tree *dst; struct dsa_switch_tree *dst;
int index; int index;
/* Listener for switch fabric events */
struct notifier_block nb;
/* /*
* Give the switch driver somewhere to hang its private data * Give the switch driver somewhere to hang its private data
* structure. * structure.
...@@ -261,6 +268,16 @@ struct switchdev_obj_port_fdb; ...@@ -261,6 +268,16 @@ struct switchdev_obj_port_fdb;
struct switchdev_obj_port_mdb; struct switchdev_obj_port_mdb;
struct switchdev_obj_port_vlan; struct switchdev_obj_port_vlan;
#define DSA_NOTIFIER_BRIDGE_JOIN 1
#define DSA_NOTIFIER_BRIDGE_LEAVE 2
/* DSA_NOTIFIER_BRIDGE_* */
struct dsa_notifier_bridge_info {
struct net_device *br;
int sw_index;
int port;
};
struct dsa_switch_ops { struct dsa_switch_ops {
/* /*
* Probing and setup. * Probing and setup.
......
# the core # the core
obj-$(CONFIG_NET_DSA) += dsa_core.o obj-$(CONFIG_NET_DSA) += dsa_core.o
dsa_core-y += dsa.o slave.o dsa2.o dsa_core-y += dsa.o slave.o dsa2.o
dsa_core-y += dsa.o slave.o dsa2.o switch.o
# tagging formats # tagging formats
dsa_core-$(CONFIG_NET_DSA_TAG_BRCM) += tag_brcm.o dsa_core-$(CONFIG_NET_DSA_TAG_BRCM) += tag_brcm.o
......
...@@ -275,6 +275,10 @@ static int dsa_switch_setup_one(struct dsa_switch *ds, struct device *parent) ...@@ -275,6 +275,10 @@ static int dsa_switch_setup_one(struct dsa_switch *ds, struct device *parent)
if (ret < 0) if (ret < 0)
return ret; return ret;
ret = dsa_switch_register_notifier(ds);
if (ret)
return ret;
if (ops->set_addr) { if (ops->set_addr) {
ret = ops->set_addr(ds, dst->master_netdev->dev_addr); ret = ops->set_addr(ds, dst->master_netdev->dev_addr);
if (ret < 0) if (ret < 0)
...@@ -400,6 +404,8 @@ static void dsa_switch_destroy(struct dsa_switch *ds) ...@@ -400,6 +404,8 @@ static void dsa_switch_destroy(struct dsa_switch *ds)
if (ds->slave_mii_bus && ds->ops->phy_read) if (ds->slave_mii_bus && ds->ops->phy_read)
mdiobus_unregister(ds->slave_mii_bus); mdiobus_unregister(ds->slave_mii_bus);
dsa_switch_unregister_notifier(ds);
} }
#ifdef CONFIG_PM_SLEEP #ifdef CONFIG_PM_SLEEP
...@@ -903,10 +909,6 @@ static struct packet_type dsa_pack_type __read_mostly = { ...@@ -903,10 +909,6 @@ static struct packet_type dsa_pack_type __read_mostly = {
.func = dsa_switch_rcv, .func = dsa_switch_rcv,
}; };
static struct notifier_block dsa_netdevice_nb __read_mostly = {
.notifier_call = dsa_slave_netdevice_event,
};
#ifdef CONFIG_PM_SLEEP #ifdef CONFIG_PM_SLEEP
static int dsa_suspend(struct device *d) static int dsa_suspend(struct device *d)
{ {
...@@ -964,7 +966,9 @@ static int __init dsa_init_module(void) ...@@ -964,7 +966,9 @@ static int __init dsa_init_module(void)
{ {
int rc; int rc;
register_netdevice_notifier(&dsa_netdevice_nb); rc = dsa_slave_register_notifier();
if (rc)
return rc;
rc = platform_driver_register(&dsa_driver); rc = platform_driver_register(&dsa_driver);
if (rc) if (rc)
...@@ -978,7 +982,7 @@ module_init(dsa_init_module); ...@@ -978,7 +982,7 @@ module_init(dsa_init_module);
static void __exit dsa_cleanup_module(void) static void __exit dsa_cleanup_module(void)
{ {
unregister_netdevice_notifier(&dsa_netdevice_nb); dsa_slave_unregister_notifier();
dev_remove_pack(&dsa_pack_type); dev_remove_pack(&dsa_pack_type);
platform_driver_unregister(&dsa_driver); platform_driver_unregister(&dsa_driver);
} }
......
...@@ -294,6 +294,10 @@ static int dsa_ds_apply(struct dsa_switch_tree *dst, struct dsa_switch *ds) ...@@ -294,6 +294,10 @@ static int dsa_ds_apply(struct dsa_switch_tree *dst, struct dsa_switch *ds)
if (err < 0) if (err < 0)
return err; return err;
err = dsa_switch_register_notifier(ds);
if (err)
return err;
if (ds->ops->set_addr) { if (ds->ops->set_addr) {
err = ds->ops->set_addr(ds, dst->master_netdev->dev_addr); err = ds->ops->set_addr(ds, dst->master_netdev->dev_addr);
if (err < 0) if (err < 0)
...@@ -364,6 +368,8 @@ static void dsa_ds_unapply(struct dsa_switch_tree *dst, struct dsa_switch *ds) ...@@ -364,6 +368,8 @@ static void dsa_ds_unapply(struct dsa_switch_tree *dst, struct dsa_switch *ds)
if (ds->slave_mii_bus && ds->ops->phy_read) if (ds->slave_mii_bus && ds->ops->phy_read)
mdiobus_unregister(ds->slave_mii_bus); mdiobus_unregister(ds->slave_mii_bus);
dsa_switch_unregister_notifier(ds);
} }
static int dsa_dst_apply(struct dsa_switch_tree *dst) static int dsa_dst_apply(struct dsa_switch_tree *dst)
......
...@@ -63,8 +63,12 @@ int dsa_slave_create(struct dsa_switch *ds, struct device *parent, ...@@ -63,8 +63,12 @@ int dsa_slave_create(struct dsa_switch *ds, struct device *parent,
void dsa_slave_destroy(struct net_device *slave_dev); void dsa_slave_destroy(struct net_device *slave_dev);
int dsa_slave_suspend(struct net_device *slave_dev); int dsa_slave_suspend(struct net_device *slave_dev);
int dsa_slave_resume(struct net_device *slave_dev); int dsa_slave_resume(struct net_device *slave_dev);
int dsa_slave_netdevice_event(struct notifier_block *unused, int dsa_slave_register_notifier(void);
unsigned long event, void *ptr); void dsa_slave_unregister_notifier(void);
/* switch.c */
int dsa_switch_register_notifier(struct dsa_switch *ds);
void dsa_switch_unregister_notifier(struct dsa_switch *ds);
/* tag_dsa.c */ /* tag_dsa.c */
extern const struct dsa_device_ops dsa_netdev_ops; extern const struct dsa_device_ops dsa_netdev_ops;
......
...@@ -27,6 +27,17 @@ ...@@ -27,6 +27,17 @@
static bool dsa_slave_dev_check(struct net_device *dev); static bool dsa_slave_dev_check(struct net_device *dev);
static int dsa_slave_notify(struct net_device *dev, unsigned long e, void *v)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct raw_notifier_head *nh = &p->dp->ds->dst->nh;
int err;
err = raw_notifier_call_chain(nh, e, v);
return notifier_to_errno(err);
}
/* slave mii_bus handling ***************************************************/ /* slave mii_bus handling ***************************************************/
static int dsa_slave_phy_read(struct mii_bus *bus, int addr, int reg) static int dsa_slave_phy_read(struct mii_bus *bus, int addr, int reg)
{ {
...@@ -74,9 +85,12 @@ static inline bool dsa_port_is_bridged(struct dsa_port *dp) ...@@ -74,9 +85,12 @@ static inline bool dsa_port_is_bridged(struct dsa_port *dp)
return !!dp->bridge_dev; return !!dp->bridge_dev;
} }
static void dsa_port_set_stp_state(struct dsa_switch *ds, int port, u8 state) static void dsa_slave_set_state(struct net_device *dev, u8 state)
{ {
struct dsa_port *dp = &ds->ports[port]; struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_port *dp = p->dp;
struct dsa_switch *ds = dp->ds;
int port = dp->index;
if (ds->ops->port_stp_state_set) if (ds->ops->port_stp_state_set)
ds->ops->port_stp_state_set(ds, port, state); ds->ops->port_stp_state_set(ds, port, state);
...@@ -133,7 +147,7 @@ static int dsa_slave_open(struct net_device *dev) ...@@ -133,7 +147,7 @@ static int dsa_slave_open(struct net_device *dev)
goto clear_promisc; goto clear_promisc;
} }
dsa_port_set_stp_state(ds, p->dp->index, stp_state); dsa_slave_set_state(dev, stp_state);
if (p->phy) if (p->phy)
phy_start(p->phy); phy_start(p->phy);
...@@ -175,7 +189,7 @@ static int dsa_slave_close(struct net_device *dev) ...@@ -175,7 +189,7 @@ static int dsa_slave_close(struct net_device *dev)
if (ds->ops->port_disable) if (ds->ops->port_disable)
ds->ops->port_disable(ds, p->dp->index, p->phy); ds->ops->port_disable(ds, p->dp->index, p->phy);
dsa_port_set_stp_state(ds, p->dp->index, BR_STATE_DISABLED); dsa_slave_set_state(dev, BR_STATE_DISABLED);
return 0; return 0;
} }
...@@ -382,7 +396,7 @@ static int dsa_slave_stp_state_set(struct net_device *dev, ...@@ -382,7 +396,7 @@ static int dsa_slave_stp_state_set(struct net_device *dev,
if (switchdev_trans_ph_prepare(trans)) if (switchdev_trans_ph_prepare(trans))
return ds->ops->port_stp_state_set ? 0 : -EOPNOTSUPP; return ds->ops->port_stp_state_set ? 0 : -EOPNOTSUPP;
dsa_port_set_stp_state(ds, p->dp->index, attr->u.stp_state); dsa_slave_set_state(dev, attr->u.stp_state);
return 0; return 0;
} }
...@@ -559,32 +573,51 @@ static int dsa_slave_bridge_port_join(struct net_device *dev, ...@@ -559,32 +573,51 @@ static int dsa_slave_bridge_port_join(struct net_device *dev,
struct net_device *br) struct net_device *br)
{ {
struct dsa_slave_priv *p = netdev_priv(dev); struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->dp->ds; struct dsa_notifier_bridge_info info = {
int ret = -EOPNOTSUPP; .sw_index = p->dp->ds->index,
.port = p->dp->index,
.br = br,
};
int err;
/* Here the port is already bridged. Reflect the current configuration
* so that drivers can program their chips accordingly.
*/
p->dp->bridge_dev = br; p->dp->bridge_dev = br;
if (ds->ops->port_bridge_join) err = dsa_slave_notify(dev, DSA_NOTIFIER_BRIDGE_JOIN, &info);
ret = ds->ops->port_bridge_join(ds, p->dp->index, br);
return ret == -EOPNOTSUPP ? 0 : ret; /* The bridging is rolled back on error */
if (err)
p->dp->bridge_dev = NULL;
return err;
} }
static void dsa_slave_bridge_port_leave(struct net_device *dev, static void dsa_slave_bridge_port_leave(struct net_device *dev,
struct net_device *br) struct net_device *br)
{ {
struct dsa_slave_priv *p = netdev_priv(dev); struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->dp->ds; struct dsa_notifier_bridge_info info = {
.sw_index = p->dp->ds->index,
.port = p->dp->index,
.br = br,
};
int err;
/* Here the port is already unbridged. Reflect the current configuration
* so that drivers can program their chips accordingly.
*/
p->dp->bridge_dev = NULL; p->dp->bridge_dev = NULL;
if (ds->ops->port_bridge_leave) err = dsa_slave_notify(dev, DSA_NOTIFIER_BRIDGE_LEAVE, &info);
ds->ops->port_bridge_leave(ds, p->dp->index, br); if (err)
netdev_err(dev, "failed to notify DSA_NOTIFIER_BRIDGE_LEAVE\n");
/* Port left the bridge, put in BR_STATE_DISABLED by the bridge layer, /* Port left the bridge, put in BR_STATE_DISABLED by the bridge layer,
* so allow it to be in BR_STATE_FORWARDING to be kept functional * so allow it to be in BR_STATE_FORWARDING to be kept functional
*/ */
dsa_port_set_stp_state(ds, p->dp->index, BR_STATE_FORWARDING); dsa_slave_set_state(dev, BR_STATE_FORWARDING);
} }
static int dsa_slave_port_attr_get(struct net_device *dev, static int dsa_slave_port_attr_get(struct net_device *dev,
...@@ -1491,46 +1524,52 @@ static bool dsa_slave_dev_check(struct net_device *dev) ...@@ -1491,46 +1524,52 @@ static bool dsa_slave_dev_check(struct net_device *dev)
return dev->netdev_ops == &dsa_slave_netdev_ops; return dev->netdev_ops == &dsa_slave_netdev_ops;
} }
static int dsa_slave_port_upper_event(struct net_device *dev, static int dsa_slave_changeupper(struct net_device *dev,
unsigned long event, void *ptr) struct netdev_notifier_changeupper_info *info)
{ {
struct netdev_notifier_changeupper_info *info = ptr; int err = NOTIFY_DONE;
struct net_device *upper = info->upper_dev;
int err = 0;
switch (event) { if (netif_is_bridge_master(info->upper_dev)) {
case NETDEV_CHANGEUPPER: if (info->linking) {
if (netif_is_bridge_master(upper)) { err = dsa_slave_bridge_port_join(dev, info->upper_dev);
if (info->linking) err = notifier_from_errno(err);
err = dsa_slave_bridge_port_join(dev, upper); } else {
else dsa_slave_bridge_port_leave(dev, info->upper_dev);
dsa_slave_bridge_port_leave(dev, upper); err = NOTIFY_OK;
} }
break;
} }
return notifier_from_errno(err); return err;
} }
static int dsa_slave_port_event(struct net_device *dev, unsigned long event, static int dsa_slave_netdevice_event(struct notifier_block *nb,
void *ptr) unsigned long event, void *ptr)
{ {
switch (event) { struct net_device *dev = netdev_notifier_info_to_dev(ptr);
case NETDEV_CHANGEUPPER:
return dsa_slave_port_upper_event(dev, event, ptr); if (dev->netdev_ops != &dsa_slave_netdev_ops)
} return NOTIFY_DONE;
if (event == NETDEV_CHANGEUPPER)
return dsa_slave_changeupper(dev, ptr);
return NOTIFY_DONE; return NOTIFY_DONE;
} }
int dsa_slave_netdevice_event(struct notifier_block *unused, static struct notifier_block dsa_slave_nb __read_mostly = {
unsigned long event, void *ptr) .notifier_call = dsa_slave_netdevice_event,
};
int dsa_slave_register_notifier(void)
{ {
struct net_device *dev = netdev_notifier_info_to_dev(ptr); return register_netdevice_notifier(&dsa_slave_nb);
}
if (dsa_slave_dev_check(dev)) void dsa_slave_unregister_notifier(void)
return dsa_slave_port_event(dev, event, ptr); {
int err;
return NOTIFY_DONE; err = unregister_netdevice_notifier(&dsa_slave_nb);
if (err)
pr_err("DSA: failed to unregister slave notifier (%d)\n", err);
} }
/*
* Handling of a single switch chip, part of a switch fabric
*
* Copyright (c) 2017 Vivien Didelot <vivien.didelot@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/netdevice.h>
#include <linux/notifier.h>
#include <net/dsa.h>
static int dsa_switch_bridge_join(struct dsa_switch *ds,
struct dsa_notifier_bridge_info *info)
{
if (ds->index == info->sw_index && ds->ops->port_bridge_join)
return ds->ops->port_bridge_join(ds, info->port, info->br);
if (ds->index != info->sw_index)
dev_dbg(ds->dev, "crosschip DSA port %d.%d bridged to %s\n",
info->sw_index, info->port, netdev_name(info->br));
return 0;
}
static int dsa_switch_bridge_leave(struct dsa_switch *ds,
struct dsa_notifier_bridge_info *info)
{
if (ds->index == info->sw_index && ds->ops->port_bridge_leave)
ds->ops->port_bridge_leave(ds, info->port, info->br);
if (ds->index != info->sw_index)
dev_dbg(ds->dev, "crosschip DSA port %d.%d unbridged from %s\n",
info->sw_index, info->port, netdev_name(info->br));
return 0;
}
static int dsa_switch_event(struct notifier_block *nb,
unsigned long event, void *info)
{
struct dsa_switch *ds = container_of(nb, struct dsa_switch, nb);
int err;
switch (event) {
case DSA_NOTIFIER_BRIDGE_JOIN:
err = dsa_switch_bridge_join(ds, info);
break;
case DSA_NOTIFIER_BRIDGE_LEAVE:
err = dsa_switch_bridge_leave(ds, info);
break;
default:
err = -EOPNOTSUPP;
break;
}
/* Non-switchdev operations cannot be rolled back. If a DSA driver
* returns an error during the chained call, switch chips may be in an
* inconsistent state.
*/
if (err)
dev_dbg(ds->dev, "breaking chain for DSA event %lu (%d)\n",
event, err);
return notifier_from_errno(err);
}
int dsa_switch_register_notifier(struct dsa_switch *ds)
{
ds->nb.notifier_call = dsa_switch_event;
return raw_notifier_chain_register(&ds->dst->nh, &ds->nb);
}
void dsa_switch_unregister_notifier(struct dsa_switch *ds)
{
int err;
err = raw_notifier_chain_unregister(&ds->dst->nh, &ds->nb);
if (err)
dev_err(ds->dev, "failed to unregister notifier (%d)\n", err);
}
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