Merge branch 'misc' of git://git.kernel.org/pub/scm/linux/kernel/git/mmarek/kbuild
[firefly-linux-kernel-4.4.55.git] / net / dsa / slave.c
index f23deadf42a070a251a7fc0c8f7b4d0a1e767dc5..827cda560a552b7b0dca45d49a06816e6dda513b 100644 (file)
 
 #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;
 }