|
@@ -10,10 +10,13 @@
|
|
|
|
|
|
#include <linux/list.h>
|
|
#include <linux/list.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/etherdevice.h>
|
|
|
|
+#include <linux/netdevice.h>
|
|
#include <linux/phy.h>
|
|
#include <linux/phy.h>
|
|
#include <linux/phy_fixed.h>
|
|
#include <linux/phy_fixed.h>
|
|
#include <linux/of_net.h>
|
|
#include <linux/of_net.h>
|
|
#include <linux/of_mdio.h>
|
|
#include <linux/of_mdio.h>
|
|
|
|
+#include <net/rtnetlink.h>
|
|
|
|
+#include <linux/if_bridge.h>
|
|
#include "dsa_priv.h"
|
|
#include "dsa_priv.h"
|
|
|
|
|
|
/* slave mii_bus handling ***************************************************/
|
|
/* slave mii_bus handling ***************************************************/
|
|
@@ -60,11 +63,18 @@ static int dsa_slave_init(struct net_device *dev)
|
|
return 0;
|
|
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)
|
|
static int dsa_slave_open(struct net_device *dev)
|
|
{
|
|
{
|
|
struct dsa_slave_priv *p = netdev_priv(dev);
|
|
struct dsa_slave_priv *p = netdev_priv(dev);
|
|
struct net_device *master = p->parent->dst->master_netdev;
|
|
struct net_device *master = p->parent->dst->master_netdev;
|
|
struct dsa_switch *ds = p->parent;
|
|
struct dsa_switch *ds = p->parent;
|
|
|
|
+ u8 stp_state = dsa_port_is_bridged(p) ?
|
|
|
|
+ BR_STATE_BLOCKING : BR_STATE_FORWARDING;
|
|
int err;
|
|
int err;
|
|
|
|
|
|
if (!(master->flags & IFF_UP))
|
|
if (!(master->flags & IFF_UP))
|
|
@@ -93,6 +103,9 @@ static int dsa_slave_open(struct net_device *dev)
|
|
goto clear_promisc;
|
|
goto clear_promisc;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ if (ds->drv->port_stp_update)
|
|
|
|
+ ds->drv->port_stp_update(ds, p->port, stp_state);
|
|
|
|
+
|
|
if (p->phy)
|
|
if (p->phy)
|
|
phy_start(p->phy);
|
|
phy_start(p->phy);
|
|
|
|
|
|
@@ -133,6 +146,9 @@ static int dsa_slave_close(struct net_device *dev)
|
|
if (ds->drv->port_disable)
|
|
if (ds->drv->port_disable)
|
|
ds->drv->port_disable(ds, p->port, p->phy);
|
|
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;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -194,6 +210,95 @@ static int dsa_slave_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
|
|
return -EOPNOTSUPP;
|
|
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 (!((1 << port) & ds->phys_port_mask))
|
|
|
|
+ continue;
|
|
|
|
+
|
|
|
|
+ if (!ds->ports[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)
|
|
static netdev_tx_t dsa_slave_xmit(struct sk_buff *skb, struct net_device *dev)
|
|
{
|
|
{
|
|
struct dsa_slave_priv *p = netdev_priv(dev);
|
|
struct dsa_slave_priv *p = netdev_priv(dev);
|
|
@@ -470,6 +575,8 @@ static const struct net_device_ops dsa_slave_netdev_ops = {
|
|
.ndo_set_rx_mode = dsa_slave_set_rx_mode,
|
|
.ndo_set_rx_mode = dsa_slave_set_rx_mode,
|
|
.ndo_set_mac_address = dsa_slave_set_mac_address,
|
|
.ndo_set_mac_address = dsa_slave_set_mac_address,
|
|
.ndo_do_ioctl = dsa_slave_ioctl,
|
|
.ndo_do_ioctl = dsa_slave_ioctl,
|
|
|
|
+ .ndo_switch_parent_id_get = dsa_slave_parent_id_get,
|
|
|
|
+ .ndo_switch_port_stp_update = dsa_slave_stp_update,
|
|
};
|
|
};
|
|
|
|
|
|
static void dsa_slave_adjust_link(struct net_device *dev)
|
|
static void dsa_slave_adjust_link(struct net_device *dev)
|
|
@@ -684,3 +791,45 @@ int dsa_slave_create(struct dsa_switch *ds, struct device *parent,
|
|
|
|
|
|
return 0;
|
|
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);
|
|
|
|
+ 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
|
|
|
|
+ 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;
|
|
|
|
+}
|