summaryrefslogtreecommitdiffstats
path: root/net/dsa
diff options
context:
space:
mode:
Diffstat (limited to 'net/dsa')
-rw-r--r--net/dsa/Kconfig7
-rw-r--r--net/dsa/dsa.c251
-rw-r--r--net/dsa/dsa_priv.h9
-rw-r--r--net/dsa/slave.c327
4 files changed, 459 insertions, 135 deletions
diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig
index 5f8ac404535b..ff7736f7ff42 100644
--- a/net/dsa/Kconfig
+++ b/net/dsa/Kconfig
@@ -5,9 +5,12 @@ config HAVE_NET_DSA
# Drivers must select NET_DSA and the appropriate tagging format
config NET_DSA
- tristate
- depends on HAVE_NET_DSA
+ tristate "Distributed Switch Architecture"
+ depends on HAVE_NET_DSA && NET_SWITCHDEV
select PHYLIB
+ ---help---
+ Say Y if you want to enable support for the hardware switches supported
+ by the Distributed Switch Architecture.
if NET_DSA
diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c
index 4dea2e0681d1..5eaadabe23a1 100644
--- a/net/dsa/dsa.c
+++ b/net/dsa/dsa.c
@@ -20,6 +20,7 @@
#include <linux/of.h>
#include <linux/of_mdio.h>
#include <linux/of_platform.h>
+#include <linux/of_net.h>
#include <linux/sysfs.h>
#include "dsa_priv.h"
@@ -175,43 +176,14 @@ __ATTRIBUTE_GROUPS(dsa_hwmon);
#endif /* CONFIG_NET_DSA_HWMON */
/* basic switch operations **************************************************/
-static struct dsa_switch *
-dsa_switch_setup(struct dsa_switch_tree *dst, int index,
- struct device *parent, struct device *host_dev)
+static int dsa_switch_setup_one(struct dsa_switch *ds, struct device *parent)
{
- struct dsa_chip_data *pd = dst->pd->chip + index;
- struct dsa_switch_driver *drv;
- struct dsa_switch *ds;
- int ret;
- char *name;
- int i;
+ struct dsa_switch_driver *drv = ds->drv;
+ struct dsa_switch_tree *dst = ds->dst;
+ struct dsa_chip_data *pd = ds->pd;
bool valid_name_found = false;
-
- /*
- * Probe for switch model.
- */
- drv = dsa_switch_probe(host_dev, pd->sw_addr, &name);
- if (drv == NULL) {
- netdev_err(dst->master_netdev, "[%d]: could not detect attached switch\n",
- index);
- return ERR_PTR(-EINVAL);
- }
- netdev_info(dst->master_netdev, "[%d]: detected a %s switch\n",
- index, name);
-
-
- /*
- * Allocate and initialise switch state.
- */
- ds = kzalloc(sizeof(*ds) + drv->priv_size, GFP_KERNEL);
- if (ds == NULL)
- return ERR_PTR(-ENOMEM);
-
- ds->dst = dst;
- ds->index = index;
- ds->pd = dst->pd->chip + index;
- ds->drv = drv;
- ds->master_dev = host_dev;
+ int index = ds->index;
+ int i, ret;
/*
* Validate supplied switch configuration.
@@ -256,7 +228,7 @@ dsa_switch_setup(struct dsa_switch_tree *dst, int index,
* switch.
*/
if (dst->cpu_switch == index) {
- switch (drv->tag_protocol) {
+ switch (ds->tag_protocol) {
#ifdef CONFIG_NET_DSA_TAG_DSA
case DSA_TAG_PROTO_DSA:
dst->rcv = dsa_netdev_ops.rcv;
@@ -284,7 +256,7 @@ dsa_switch_setup(struct dsa_switch_tree *dst, int index,
goto out;
}
- dst->tag_protocol = drv->tag_protocol;
+ dst->tag_protocol = ds->tag_protocol;
}
/*
@@ -314,19 +286,15 @@ dsa_switch_setup(struct dsa_switch_tree *dst, int index,
* Create network devices for physical switch ports.
*/
for (i = 0; i < DSA_MAX_PORTS; i++) {
- struct net_device *slave_dev;
-
if (!(ds->phys_port_mask & (1 << i)))
continue;
- slave_dev = dsa_slave_create(ds, parent, i, pd->port_names[i]);
- if (slave_dev == NULL) {
+ ret = dsa_slave_create(ds, parent, i, pd->port_names[i]);
+ if (ret < 0) {
netdev_err(dst->master_netdev, "[%d]: can't create dsa slave device for port %d(%s)\n",
index, i, pd->port_names[i]);
- continue;
+ ret = 0;
}
-
- ds->ports[i] = slave_dev;
}
#ifdef CONFIG_NET_DSA_HWMON
@@ -354,13 +322,57 @@ dsa_switch_setup(struct dsa_switch_tree *dst, int index,
}
#endif /* CONFIG_NET_DSA_HWMON */
- return ds;
+ return ret;
out_free:
mdiobus_free(ds->slave_mii_bus);
out:
kfree(ds);
- return ERR_PTR(ret);
+ return ret;
+}
+
+static struct dsa_switch *
+dsa_switch_setup(struct dsa_switch_tree *dst, int index,
+ struct device *parent, struct device *host_dev)
+{
+ struct dsa_chip_data *pd = dst->pd->chip + index;
+ struct dsa_switch_driver *drv;
+ struct dsa_switch *ds;
+ int ret;
+ char *name;
+
+ /*
+ * Probe for switch model.
+ */
+ drv = dsa_switch_probe(host_dev, pd->sw_addr, &name);
+ if (drv == NULL) {
+ netdev_err(dst->master_netdev, "[%d]: could not detect attached switch\n",
+ index);
+ return ERR_PTR(-EINVAL);
+ }
+ netdev_info(dst->master_netdev, "[%d]: detected a %s switch\n",
+ index, name);
+
+
+ /*
+ * Allocate and initialise switch state.
+ */
+ ds = kzalloc(sizeof(*ds) + drv->priv_size, GFP_KERNEL);
+ if (ds == NULL)
+ return NULL;
+
+ ds->dst = dst;
+ ds->index = index;
+ ds->pd = pd;
+ ds->drv = drv;
+ ds->tag_protocol = drv->tag_protocol;
+ ds->master_dev = host_dev;
+
+ ret = dsa_switch_setup_one(ds, parent);
+ if (ret)
+ return NULL;
+
+ return ds;
}
static void dsa_switch_destroy(struct dsa_switch *ds)
@@ -378,7 +390,7 @@ static int dsa_switch_suspend(struct dsa_switch *ds)
/* Suspend slave network devices */
for (i = 0; i < DSA_MAX_PORTS; i++) {
- if (!(ds->phys_port_mask & (1 << i)))
+ if (!dsa_is_port_initialized(ds, i))
continue;
ret = dsa_slave_suspend(ds->ports[i]);
@@ -404,7 +416,7 @@ static int dsa_switch_resume(struct dsa_switch *ds)
/* Resume slave network devices */
for (i = 0; i < DSA_MAX_PORTS; i++) {
- if (!(ds->phys_port_mask & (1 << i)))
+ if (!dsa_is_port_initialized(ds, i))
continue;
ret = dsa_slave_resume(ds->ports[i]);
@@ -558,12 +570,12 @@ static void dsa_of_free_platform_data(struct dsa_platform_data *pd)
kfree(pd->chip);
}
-static int dsa_of_probe(struct platform_device *pdev)
+static int dsa_of_probe(struct device *dev)
{
- struct device_node *np = pdev->dev.of_node;
+ struct device_node *np = dev->of_node;
struct device_node *child, *mdio, *ethernet, *port, *link;
struct mii_bus *mdio_bus;
- struct platform_device *ethernet_dev;
+ struct net_device *ethernet_dev;
struct dsa_platform_data *pd;
struct dsa_chip_data *cd;
const char *port_name;
@@ -578,22 +590,22 @@ static int dsa_of_probe(struct platform_device *pdev)
mdio_bus = of_mdio_find_bus(mdio);
if (!mdio_bus)
- return -EINVAL;
+ return -EPROBE_DEFER;
ethernet = of_parse_phandle(np, "dsa,ethernet", 0);
if (!ethernet)
return -EINVAL;
- ethernet_dev = of_find_device_by_node(ethernet);
+ ethernet_dev = of_find_net_device_by_node(ethernet);
if (!ethernet_dev)
- return -ENODEV;
+ return -EPROBE_DEFER;
pd = kzalloc(sizeof(*pd), GFP_KERNEL);
if (!pd)
return -ENOMEM;
- pdev->dev.platform_data = pd;
- pd->netdev = &ethernet_dev->dev;
+ dev->platform_data = pd;
+ pd->of_netdev = ethernet_dev;
pd->nr_chips = of_get_available_child_count(np);
if (pd->nr_chips > DSA_MAX_SWITCHES)
pd->nr_chips = DSA_MAX_SWITCHES;
@@ -665,72 +677,35 @@ out_free_chip:
dsa_of_free_platform_data(pd);
out_free:
kfree(pd);
- pdev->dev.platform_data = NULL;
+ dev->platform_data = NULL;
return ret;
}
-static void dsa_of_remove(struct platform_device *pdev)
+static void dsa_of_remove(struct device *dev)
{
- struct dsa_platform_data *pd = pdev->dev.platform_data;
+ struct dsa_platform_data *pd = dev->platform_data;
- if (!pdev->dev.of_node)
+ if (!dev->of_node)
return;
dsa_of_free_platform_data(pd);
kfree(pd);
}
#else
-static inline int dsa_of_probe(struct platform_device *pdev)
+static inline int dsa_of_probe(struct device *dev)
{
return 0;
}
-static inline void dsa_of_remove(struct platform_device *pdev)
+static inline void dsa_of_remove(struct device *dev)
{
}
#endif
-static int dsa_probe(struct platform_device *pdev)
+static void dsa_setup_dst(struct dsa_switch_tree *dst, struct net_device *dev,
+ struct device *parent, struct dsa_platform_data *pd)
{
- struct dsa_platform_data *pd = pdev->dev.platform_data;
- struct net_device *dev;
- struct dsa_switch_tree *dst;
- int i, ret;
-
- pr_notice_once("Distributed Switch Architecture driver version %s\n",
- dsa_driver_version);
-
- if (pdev->dev.of_node) {
- ret = dsa_of_probe(pdev);
- if (ret)
- return ret;
-
- pd = pdev->dev.platform_data;
- }
-
- if (pd == NULL || pd->netdev == NULL)
- return -EINVAL;
-
- dev = dev_to_net_device(pd->netdev);
- if (dev == NULL) {
- ret = -EINVAL;
- goto out;
- }
-
- if (dev->dsa_ptr != NULL) {
- dev_put(dev);
- ret = -EEXIST;
- goto out;
- }
-
- dst = kzalloc(sizeof(*dst), GFP_KERNEL);
- if (dst == NULL) {
- dev_put(dev);
- ret = -ENOMEM;
- goto out;
- }
-
- platform_set_drvdata(pdev, dst);
+ int i;
dst->pd = pd;
dst->master_netdev = dev;
@@ -740,7 +715,7 @@ static int dsa_probe(struct platform_device *pdev)
for (i = 0; i < pd->nr_chips; i++) {
struct dsa_switch *ds;
- ds = dsa_switch_setup(dst, i, &pdev->dev, pd->chip[i].host_dev);
+ ds = dsa_switch_setup(dst, i, parent, pd->chip[i].host_dev);
if (IS_ERR(ds)) {
netdev_err(dev, "[%d]: couldn't create dsa switch instance (error %ld)\n",
i, PTR_ERR(ds));
@@ -768,18 +743,67 @@ static int dsa_probe(struct platform_device *pdev)
dst->link_poll_timer.expires = round_jiffies(jiffies + HZ);
add_timer(&dst->link_poll_timer);
}
+}
+
+static int dsa_probe(struct platform_device *pdev)
+{
+ struct dsa_platform_data *pd = pdev->dev.platform_data;
+ struct net_device *dev;
+ struct dsa_switch_tree *dst;
+ int ret;
+
+ pr_notice_once("Distributed Switch Architecture driver version %s\n",
+ dsa_driver_version);
+
+ if (pdev->dev.of_node) {
+ ret = dsa_of_probe(&pdev->dev);
+ if (ret)
+ return ret;
+
+ pd = pdev->dev.platform_data;
+ }
+
+ if (pd == NULL || (pd->netdev == NULL && pd->of_netdev == NULL))
+ return -EINVAL;
+
+ if (pd->of_netdev) {
+ dev = pd->of_netdev;
+ dev_hold(dev);
+ } else {
+ dev = dev_to_net_device(pd->netdev);
+ }
+ if (dev == NULL) {
+ ret = -EPROBE_DEFER;
+ goto out;
+ }
+
+ if (dev->dsa_ptr != NULL) {
+ dev_put(dev);
+ ret = -EEXIST;
+ goto out;
+ }
+
+ dst = kzalloc(sizeof(*dst), GFP_KERNEL);
+ if (dst == NULL) {
+ dev_put(dev);
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ platform_set_drvdata(pdev, dst);
+
+ dsa_setup_dst(dst, dev, &pdev->dev, pd);
return 0;
out:
- dsa_of_remove(pdev);
+ dsa_of_remove(&pdev->dev);
return ret;
}
-static int dsa_remove(struct platform_device *pdev)
+static void dsa_remove_dst(struct dsa_switch_tree *dst)
{
- struct dsa_switch_tree *dst = platform_get_drvdata(pdev);
int i;
if (dst->link_poll_needed)
@@ -793,8 +817,14 @@ static int dsa_remove(struct platform_device *pdev)
if (ds != NULL)
dsa_switch_destroy(ds);
}
+}
+
+static int dsa_remove(struct platform_device *pdev)
+{
+ struct dsa_switch_tree *dst = platform_get_drvdata(pdev);
- dsa_of_remove(pdev);
+ dsa_remove_dst(dst);
+ dsa_of_remove(&pdev->dev);
return 0;
}
@@ -821,6 +851,10 @@ static struct packet_type dsa_pack_type __read_mostly = {
.func = dsa_switch_rcv,
};
+static struct notifier_block dsa_netdevice_nb __read_mostly = {
+ .notifier_call = dsa_slave_netdevice_event,
+};
+
#ifdef CONFIG_PM_SLEEP
static int dsa_suspend(struct device *d)
{
@@ -879,6 +913,8 @@ static int __init dsa_init_module(void)
{
int rc;
+ register_netdevice_notifier(&dsa_netdevice_nb);
+
rc = platform_driver_register(&dsa_driver);
if (rc)
return rc;
@@ -891,6 +927,7 @@ module_init(dsa_init_module);
static void __exit dsa_cleanup_module(void)
{
+ unregister_netdevice_notifier(&dsa_netdevice_nb);
dev_remove_pack(&dsa_pack_type);
platform_driver_unregister(&dsa_driver);
}
diff --git a/net/dsa/dsa_priv.h b/net/dsa/dsa_priv.h
index dc9756d3154c..d5f1f9b862ea 100644
--- a/net/dsa/dsa_priv.h
+++ b/net/dsa/dsa_priv.h
@@ -45,6 +45,8 @@ struct dsa_slave_priv {
int old_link;
int old_pause;
int old_duplex;
+
+ struct net_device *bridge_dev;
};
/* dsa.c */
@@ -53,11 +55,12 @@ extern char dsa_driver_version[];
/* slave.c */
extern const struct dsa_device_ops notag_netdev_ops;
void dsa_slave_mii_bus_init(struct dsa_switch *ds);
-struct net_device *dsa_slave_create(struct dsa_switch *ds,
- struct device *parent,
- int port, char *name);
+int dsa_slave_create(struct dsa_switch *ds, struct device *parent,
+ int port, char *name);
int dsa_slave_suspend(struct net_device *slave_dev);
int dsa_slave_resume(struct net_device *slave_dev);
+int dsa_slave_netdevice_event(struct notifier_block *unused,
+ unsigned long event, void *ptr);
/* tag_dsa.c */
extern const struct dsa_device_ops dsa_netdev_ops;
diff --git a/net/dsa/slave.c b/net/dsa/slave.c
index f23deadf42a0..827cda560a55 100644
--- a/net/dsa/slave.c
+++ b/net/dsa/slave.c
@@ -10,10 +10,14 @@
#include <linux/list.h>
#include <linux/etherdevice.h>
+#include <linux/netdevice.h>
#include <linux/phy.h>
#include <linux/phy_fixed.h>
#include <linux/of_net.h>
#include <linux/of_mdio.h>
+#include <net/rtnetlink.h>
+#include <net/switchdev.h>
+#include <linux/if_bridge.h>
#include "dsa_priv.h"
/* slave mii_bus handling ***************************************************/
@@ -51,13 +55,16 @@ void dsa_slave_mii_bus_init(struct dsa_switch *ds)
/* slave device handling ****************************************************/
-static int dsa_slave_init(struct net_device *dev)
+static int dsa_slave_get_iflink(const struct net_device *dev)
{
struct dsa_slave_priv *p = netdev_priv(dev);
- dev->iflink = p->parent->dst->master_netdev->ifindex;
+ return p->parent->dst->master_netdev->ifindex;
+}
- return 0;
+static inline bool dsa_port_is_bridged(struct dsa_slave_priv *p)
+{
+ return !!p->bridge_dev;
}
static int dsa_slave_open(struct net_device *dev)
@@ -65,6 +72,8 @@ static int dsa_slave_open(struct net_device *dev)
struct dsa_slave_priv *p = netdev_priv(dev);
struct net_device *master = p->parent->dst->master_netdev;
struct dsa_switch *ds = p->parent;
+ u8 stp_state = dsa_port_is_bridged(p) ?
+ BR_STATE_BLOCKING : BR_STATE_FORWARDING;
int err;
if (!(master->flags & IFF_UP))
@@ -93,6 +102,9 @@ static int dsa_slave_open(struct net_device *dev)
goto clear_promisc;
}
+ if (ds->drv->port_stp_update)
+ ds->drv->port_stp_update(ds, p->port, stp_state);
+
if (p->phy)
phy_start(p->phy);
@@ -133,6 +145,9 @@ static int dsa_slave_close(struct net_device *dev)
if (ds->drv->port_disable)
ds->drv->port_disable(ds, p->port, p->phy);
+ if (ds->drv->port_stp_update)
+ ds->drv->port_stp_update(ds, p->port, BR_STATE_DISABLED);
+
return 0;
}
@@ -184,6 +199,105 @@ out:
return 0;
}
+static int dsa_slave_fdb_add(struct ndmsg *ndm, struct nlattr *tb[],
+ struct net_device *dev,
+ const unsigned char *addr, u16 vid, u16 nlm_flags)
+{
+ struct dsa_slave_priv *p = netdev_priv(dev);
+ struct dsa_switch *ds = p->parent;
+ int ret = -EOPNOTSUPP;
+
+ if (ds->drv->fdb_add)
+ ret = ds->drv->fdb_add(ds, p->port, addr, vid);
+
+ return ret;
+}
+
+static int dsa_slave_fdb_del(struct ndmsg *ndm, struct nlattr *tb[],
+ struct net_device *dev,
+ const unsigned char *addr, u16 vid)
+{
+ struct dsa_slave_priv *p = netdev_priv(dev);
+ struct dsa_switch *ds = p->parent;
+ int ret = -EOPNOTSUPP;
+
+ if (ds->drv->fdb_del)
+ ret = ds->drv->fdb_del(ds, p->port, addr, vid);
+
+ return ret;
+}
+
+static int dsa_slave_fill_info(struct net_device *dev, struct sk_buff *skb,
+ const unsigned char *addr, u16 vid,
+ bool is_static,
+ u32 portid, u32 seq, int type,
+ unsigned int flags)
+{
+ struct nlmsghdr *nlh;
+ struct ndmsg *ndm;
+
+ nlh = nlmsg_put(skb, portid, seq, type, sizeof(*ndm), flags);
+ if (!nlh)
+ return -EMSGSIZE;
+
+ ndm = nlmsg_data(nlh);
+ ndm->ndm_family = AF_BRIDGE;
+ ndm->ndm_pad1 = 0;
+ ndm->ndm_pad2 = 0;
+ ndm->ndm_flags = NTF_EXT_LEARNED;
+ ndm->ndm_type = 0;
+ ndm->ndm_ifindex = dev->ifindex;
+ ndm->ndm_state = is_static ? NUD_NOARP : NUD_REACHABLE;
+
+ if (nla_put(skb, NDA_LLADDR, ETH_ALEN, addr))
+ goto nla_put_failure;
+
+ if (vid && nla_put_u16(skb, NDA_VLAN, vid))
+ goto nla_put_failure;
+
+ nlmsg_end(skb, nlh);
+ return 0;
+
+nla_put_failure:
+ nlmsg_cancel(skb, nlh);
+ return -EMSGSIZE;
+}
+
+/* Dump information about entries, in response to GETNEIGH */
+static int dsa_slave_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb,
+ struct net_device *dev,
+ struct net_device *filter_dev, int idx)
+{
+ struct dsa_slave_priv *p = netdev_priv(dev);
+ struct dsa_switch *ds = p->parent;
+ unsigned char addr[ETH_ALEN] = { 0 };
+ int ret;
+
+ if (!ds->drv->fdb_getnext)
+ return -EOPNOTSUPP;
+
+ for (; ; idx++) {
+ bool is_static;
+
+ ret = ds->drv->fdb_getnext(ds, p->port, addr, &is_static);
+ if (ret < 0)
+ break;
+
+ if (idx < cb->args[0])
+ continue;
+
+ ret = dsa_slave_fill_info(dev, skb, addr, 0,
+ is_static,
+ NETLINK_CB(cb->skb).portid,
+ cb->nlh->nlmsg_seq,
+ RTM_NEWNEIGH, NLM_F_MULTI);
+ if (ret < 0)
+ break;
+ }
+
+ return idx;
+}
+
static int dsa_slave_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
{
struct dsa_slave_priv *p = netdev_priv(dev);
@@ -194,6 +308,92 @@ static int dsa_slave_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
return -EOPNOTSUPP;
}
+/* Return a bitmask of all ports being currently bridged within a given bridge
+ * device. Note that on leave, the mask will still return the bitmask of ports
+ * currently bridged, prior to port removal, and this is exactly what we want.
+ */
+static u32 dsa_slave_br_port_mask(struct dsa_switch *ds,
+ struct net_device *bridge)
+{
+ struct dsa_slave_priv *p;
+ unsigned int port;
+ u32 mask = 0;
+
+ for (port = 0; port < DSA_MAX_PORTS; port++) {
+ if (!dsa_is_port_initialized(ds, port))
+ continue;
+
+ p = netdev_priv(ds->ports[port]);
+
+ if (ds->ports[port]->priv_flags & IFF_BRIDGE_PORT &&
+ p->bridge_dev == bridge)
+ mask |= 1 << port;
+ }
+
+ return mask;
+}
+
+static int dsa_slave_stp_update(struct net_device *dev, u8 state)
+{
+ struct dsa_slave_priv *p = netdev_priv(dev);
+ struct dsa_switch *ds = p->parent;
+ int ret = -EOPNOTSUPP;
+
+ if (ds->drv->port_stp_update)
+ ret = ds->drv->port_stp_update(ds, p->port, state);
+
+ return ret;
+}
+
+static int dsa_slave_bridge_port_join(struct net_device *dev,
+ struct net_device *br)
+{
+ struct dsa_slave_priv *p = netdev_priv(dev);
+ struct dsa_switch *ds = p->parent;
+ int ret = -EOPNOTSUPP;
+
+ p->bridge_dev = br;
+
+ if (ds->drv->port_join_bridge)
+ ret = ds->drv->port_join_bridge(ds, p->port,
+ dsa_slave_br_port_mask(ds, br));
+
+ return ret;
+}
+
+static int dsa_slave_bridge_port_leave(struct net_device *dev)
+{
+ struct dsa_slave_priv *p = netdev_priv(dev);
+ struct dsa_switch *ds = p->parent;
+ int ret = -EOPNOTSUPP;
+
+
+ if (ds->drv->port_leave_bridge)
+ ret = ds->drv->port_leave_bridge(ds, p->port,
+ dsa_slave_br_port_mask(ds, p->bridge_dev));
+
+ p->bridge_dev = NULL;
+
+ /* 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
+ */
+ dsa_slave_stp_update(dev, BR_STATE_FORWARDING);
+
+ return ret;
+}
+
+static int dsa_slave_parent_id_get(struct net_device *dev,
+ struct netdev_phys_item_id *psid)
+{
+ struct dsa_slave_priv *p = netdev_priv(dev);
+ struct dsa_switch *ds = p->parent;
+
+ psid->id_len = sizeof(ds->index);
+ memcpy(&psid->id, &ds->index, psid->id_len);
+
+ return 0;
+}
+
static netdev_tx_t dsa_slave_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct dsa_slave_priv *p = netdev_priv(dev);
@@ -462,14 +662,22 @@ static const struct ethtool_ops dsa_slave_ethtool_ops = {
};
static const struct net_device_ops dsa_slave_netdev_ops = {
- .ndo_init = dsa_slave_init,
.ndo_open = dsa_slave_open,
.ndo_stop = dsa_slave_close,
.ndo_start_xmit = dsa_slave_xmit,
.ndo_change_rx_flags = dsa_slave_change_rx_flags,
.ndo_set_rx_mode = dsa_slave_set_rx_mode,
.ndo_set_mac_address = dsa_slave_set_mac_address,
+ .ndo_fdb_add = dsa_slave_fdb_add,
+ .ndo_fdb_del = dsa_slave_fdb_del,
+ .ndo_fdb_dump = dsa_slave_fdb_dump,
.ndo_do_ioctl = dsa_slave_ioctl,
+ .ndo_get_iflink = dsa_slave_get_iflink,
+};
+
+static const struct swdev_ops dsa_slave_swdev_ops = {
+ .swdev_parent_id_get = dsa_slave_parent_id_get,
+ .swdev_port_stp_update = dsa_slave_stp_update,
};
static void dsa_slave_adjust_link(struct net_device *dev)
@@ -513,6 +721,24 @@ static int dsa_slave_fixed_link_update(struct net_device *dev,
}
/* slave device setup *******************************************************/
+static int dsa_slave_phy_connect(struct dsa_slave_priv *p,
+ struct net_device *slave_dev,
+ int addr)
+{
+ struct dsa_switch *ds = p->parent;
+
+ p->phy = ds->slave_mii_bus->phy_map[addr];
+ if (!p->phy)
+ return -ENODEV;
+
+ /* Use already configured phy mode */
+ p->phy_interface = p->phy->interface;
+ phy_connect_direct(slave_dev, p->phy, dsa_slave_adjust_link,
+ p->phy_interface);
+
+ return 0;
+}
+
static int dsa_slave_phy_setup(struct dsa_slave_priv *p,
struct net_device *slave_dev)
{
@@ -546,10 +772,25 @@ static int dsa_slave_phy_setup(struct dsa_slave_priv *p,
if (ds->drv->get_phy_flags)
phy_flags = ds->drv->get_phy_flags(ds, p->port);
- if (phy_dn)
- p->phy = of_phy_connect(slave_dev, phy_dn,
- dsa_slave_adjust_link, phy_flags,
- p->phy_interface);
+ if (phy_dn) {
+ ret = of_mdio_parse_addr(&slave_dev->dev, phy_dn);
+ /* If this PHY address is part of phys_mii_mask, which means
+ * that we need to divert reads and writes to/from it, then we
+ * want to bind this device using the slave MII bus created by
+ * DSA to make that happen.
+ */
+ if (!phy_is_fixed && ret >= 0 &&
+ (ds->phys_mii_mask & (1 << ret))) {
+ ret = dsa_slave_phy_connect(p, slave_dev, ret);
+ if (ret)
+ return ret;
+ } else {
+ p->phy = of_phy_connect(slave_dev, phy_dn,
+ dsa_slave_adjust_link,
+ phy_flags,
+ p->phy_interface);
+ }
+ }
if (p->phy && phy_is_fixed)
fixed_phy_set_link_update(p->phy, dsa_slave_fixed_link_update);
@@ -558,14 +799,9 @@ static int dsa_slave_phy_setup(struct dsa_slave_priv *p,
* MDIO bus instead
*/
if (!p->phy) {
- p->phy = ds->slave_mii_bus->phy_map[p->port];
- if (!p->phy)
- return -ENODEV;
-
- /* Use already configured phy mode */
- p->phy_interface = p->phy->interface;
- phy_connect_direct(slave_dev, p->phy, dsa_slave_adjust_link,
- p->phy_interface);
+ ret = dsa_slave_phy_connect(p, slave_dev, p->port);
+ if (ret)
+ return ret;
} else {
netdev_info(slave_dev, "attached PHY at address %d [%s]\n",
p->phy->addr, p->phy->drv->name);
@@ -605,9 +841,8 @@ int dsa_slave_resume(struct net_device *slave_dev)
return 0;
}
-struct net_device *
-dsa_slave_create(struct dsa_switch *ds, struct device *parent,
- int port, char *name)
+int dsa_slave_create(struct dsa_switch *ds, struct device *parent,
+ int port, char *name)
{
struct net_device *master = ds->dst->master_netdev;
struct net_device *slave_dev;
@@ -617,13 +852,14 @@ dsa_slave_create(struct dsa_switch *ds, struct device *parent,
slave_dev = alloc_netdev(sizeof(struct dsa_slave_priv), name,
NET_NAME_UNKNOWN, ether_setup);
if (slave_dev == NULL)
- return slave_dev;
+ return -ENOMEM;
slave_dev->features = master->vlan_features;
slave_dev->ethtool_ops = &dsa_slave_ethtool_ops;
eth_hw_addr_inherit(slave_dev, master);
slave_dev->tx_queue_len = 0;
slave_dev->netdev_ops = &dsa_slave_netdev_ops;
+ slave_dev->swdev_ops = &dsa_slave_swdev_ops;
SET_NETDEV_DEV(slave_dev, parent);
slave_dev->dev.of_node = ds->pd->port_dn[port];
@@ -667,19 +903,64 @@ dsa_slave_create(struct dsa_switch *ds, struct device *parent,
ret = dsa_slave_phy_setup(p, slave_dev);
if (ret) {
free_netdev(slave_dev);
- return NULL;
+ return ret;
}
+ ds->ports[port] = slave_dev;
ret = register_netdev(slave_dev);
if (ret) {
netdev_err(master, "error %d registering interface %s\n",
ret, slave_dev->name);
phy_disconnect(p->phy);
+ ds->ports[port] = NULL;
free_netdev(slave_dev);
- return NULL;
+ return ret;
}
netif_carrier_off(slave_dev);
- return slave_dev;
+ return 0;
+}
+
+static bool dsa_slave_dev_check(struct net_device *dev)
+{
+ return dev->netdev_ops == &dsa_slave_netdev_ops;
+}
+
+static int dsa_slave_master_changed(struct net_device *dev)
+{
+ struct net_device *master = netdev_master_upper_dev_get(dev);
+ struct dsa_slave_priv *p = netdev_priv(dev);
+ int err = 0;
+
+ if (master && master->rtnl_link_ops &&
+ !strcmp(master->rtnl_link_ops->kind, "bridge"))
+ err = dsa_slave_bridge_port_join(dev, master);
+ else if (dsa_port_is_bridged(p))
+ err = dsa_slave_bridge_port_leave(dev);
+
+ return err;
+}
+
+int dsa_slave_netdevice_event(struct notifier_block *unused,
+ unsigned long event, void *ptr)
+{
+ struct net_device *dev;
+ int err = 0;
+
+ switch (event) {
+ case NETDEV_CHANGEUPPER:
+ dev = netdev_notifier_info_to_dev(ptr);
+ if (!dsa_slave_dev_check(dev))
+ goto out;
+
+ err = dsa_slave_master_changed(dev);
+ if (err)
+ netdev_warn(dev, "failed to reflect master change\n");
+
+ break;
+ }
+
+out:
+ return NOTIFY_DONE;
}