]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - net/dsa/slave.c
Merge tag 'for-linus' of git://git.kernel.org/pub/scm/virt/kvm/kvm
[karo-tx-linux.git] / net / dsa / slave.c
index 35c47ddd04f0ee3eb965fd99b06bab2ee4670774..7d91f4612ac07406cfffd90defb6aa1d7436cb36 100644 (file)
@@ -18,6 +18,7 @@
 #include <net/rtnetlink.h>
 #include <net/switchdev.h>
 #include <linux/if_bridge.h>
+#include <linux/netpoll.h>
 #include "dsa_priv.h"
 
 /* slave mii_bus handling ***************************************************/
@@ -199,103 +200,212 @@ 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)
+static int dsa_bridge_check_vlan_range(struct dsa_switch *ds,
+                                      const struct net_device *bridge,
+                                      u16 vid_begin, u16 vid_end)
 {
+       struct dsa_slave_priv *p;
+       struct net_device *dev, *vlan_br;
+       DECLARE_BITMAP(members, DSA_MAX_PORTS);
+       DECLARE_BITMAP(untagged, DSA_MAX_PORTS);
+       u16 vid;
+       int member, err;
+
+       if (!ds->drv->vlan_getnext || !vid_begin)
+               return -EOPNOTSUPP;
+
+       vid = vid_begin - 1;
+
+       do {
+               err = ds->drv->vlan_getnext(ds, &vid, members, untagged);
+               if (err)
+                       break;
+
+               if (vid > vid_end)
+                       break;
+
+               member = find_first_bit(members, DSA_MAX_PORTS);
+               if (member == DSA_MAX_PORTS)
+                       continue;
+
+               dev = ds->ports[member];
+               p = netdev_priv(dev);
+               vlan_br = p->bridge_dev;
+               if (vlan_br == bridge)
+                       continue;
+
+               netdev_dbg(vlan_br, "hardware VLAN %d already in use\n", vid);
+               return -EOPNOTSUPP;
+       } while (vid < vid_end);
+
+       return err == -ENOENT ? 0 : err;
+}
+
+static int dsa_slave_port_vlan_add(struct net_device *dev,
+                                  struct switchdev_obj *obj)
+{
+       struct switchdev_obj_vlan *vlan = &obj->u.vlan;
        struct dsa_slave_priv *p = netdev_priv(dev);
        struct dsa_switch *ds = p->parent;
-       int ret = -EOPNOTSUPP;
+       u16 vid;
+       int err;
 
-       if (ds->drv->fdb_add)
-               ret = ds->drv->fdb_add(ds, p->port, addr, vid);
+       switch (obj->trans) {
+       case SWITCHDEV_TRANS_PREPARE:
+               if (!ds->drv->port_vlan_add || !ds->drv->port_pvid_set)
+                       return -EOPNOTSUPP;
 
-       return ret;
+               /* If the requested port doesn't belong to the same bridge as
+                * the VLAN members, fallback to software VLAN (hopefully).
+                */
+               err = dsa_bridge_check_vlan_range(ds, p->bridge_dev,
+                                                 vlan->vid_begin,
+                                                 vlan->vid_end);
+               if (err)
+                       return err;
+               break;
+       case SWITCHDEV_TRANS_COMMIT:
+               for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) {
+                       err = ds->drv->port_vlan_add(ds, p->port, vid,
+                                                    vlan->flags &
+                                                    BRIDGE_VLAN_INFO_UNTAGGED);
+                       if (!err && vlan->flags & BRIDGE_VLAN_INFO_PVID)
+                               err = ds->drv->port_pvid_set(ds, p->port, vid);
+                       if (err)
+                               return err;
+               }
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
+
+       return 0;
 }
 
-static int dsa_slave_fdb_del(struct ndmsg *ndm, struct nlattr *tb[],
-                            struct net_device *dev,
-                            const unsigned char *addr, u16 vid)
+static int dsa_slave_port_vlan_del(struct net_device *dev,
+                                  struct switchdev_obj *obj)
 {
+       struct switchdev_obj_vlan *vlan = &obj->u.vlan;
        struct dsa_slave_priv *p = netdev_priv(dev);
        struct dsa_switch *ds = p->parent;
-       int ret = -EOPNOTSUPP;
+       u16 vid;
+       int err;
 
-       if (ds->drv->fdb_del)
-               ret = ds->drv->fdb_del(ds, p->port, addr, vid);
+       if (!ds->drv->port_vlan_del)
+               return -EOPNOTSUPP;
 
-       return ret;
+       for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) {
+               err = ds->drv->port_vlan_del(ds, p->port, vid);
+               if (err)
+                       return err;
+       }
+
+       return 0;
 }
 
-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)
+static int dsa_slave_port_vlan_dump(struct net_device *dev,
+                                   struct switchdev_obj *obj)
 {
-       struct nlmsghdr *nlh;
-       struct ndmsg *ndm;
+       struct switchdev_obj_vlan *vlan = &obj->u.vlan;
+       struct dsa_slave_priv *p = netdev_priv(dev);
+       struct dsa_switch *ds = p->parent;
+       DECLARE_BITMAP(members, DSA_MAX_PORTS);
+       DECLARE_BITMAP(untagged, DSA_MAX_PORTS);
+       u16 pvid, vid = 0;
+       int err;
 
-       nlh = nlmsg_put(skb, portid, seq, type, sizeof(*ndm), flags);
-       if (!nlh)
-               return -EMSGSIZE;
+       if (!ds->drv->vlan_getnext || !ds->drv->port_pvid_get)
+               return -EOPNOTSUPP;
 
-       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;
+       err = ds->drv->port_pvid_get(ds, p->port, &pvid);
+       if (err)
+               return err;
 
-       if (nla_put(skb, NDA_LLADDR, ETH_ALEN, addr))
-               goto nla_put_failure;
+       for (;;) {
+               err = ds->drv->vlan_getnext(ds, &vid, members, untagged);
+               if (err)
+                       break;
 
-       if (vid && nla_put_u16(skb, NDA_VLAN, vid))
-               goto nla_put_failure;
+               if (!test_bit(p->port, members))
+                       continue;
 
-       nlmsg_end(skb, nlh);
-       return 0;
+               memset(vlan, 0, sizeof(*vlan));
+               vlan->vid_begin = vlan->vid_end = vid;
 
-nla_put_failure:
-       nlmsg_cancel(skb, nlh);
-       return -EMSGSIZE;
+               if (vid == pvid)
+                       vlan->flags |= BRIDGE_VLAN_INFO_PVID;
+
+               if (test_bit(p->port, untagged))
+                       vlan->flags |= BRIDGE_VLAN_INFO_UNTAGGED;
+
+               err = obj->cb(dev, obj);
+               if (err)
+                       break;
+       }
+
+       return err == -ENOENT ? 0 : err;
 }
 
-/* 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)
+static int dsa_slave_port_fdb_add(struct net_device *dev,
+                                 struct switchdev_obj *obj)
+{
+       struct switchdev_obj_fdb *fdb = &obj->u.fdb;
+       struct dsa_slave_priv *p = netdev_priv(dev);
+       struct dsa_switch *ds = p->parent;
+       int ret = -EOPNOTSUPP;
+
+       if (obj->trans == SWITCHDEV_TRANS_PREPARE)
+               ret = ds->drv->port_fdb_add ? 0 : -EOPNOTSUPP;
+       else if (obj->trans == SWITCHDEV_TRANS_COMMIT)
+               ret = ds->drv->port_fdb_add(ds, p->port, fdb->addr, fdb->vid);
+
+       return ret;
+}
+
+static int dsa_slave_port_fdb_del(struct net_device *dev,
+                                 struct switchdev_obj *obj)
+{
+       struct switchdev_obj_fdb *fdb = &obj->u.fdb;
+       struct dsa_slave_priv *p = netdev_priv(dev);
+       struct dsa_switch *ds = p->parent;
+       int ret = -EOPNOTSUPP;
+
+       if (ds->drv->port_fdb_del)
+               ret = ds->drv->port_fdb_del(ds, p->port, fdb->addr, fdb->vid);
+
+       return ret;
+}
+
+static int dsa_slave_port_fdb_dump(struct net_device *dev,
+                                  struct switchdev_obj *obj)
 {
        struct dsa_slave_priv *p = netdev_priv(dev);
        struct dsa_switch *ds = p->parent;
        unsigned char addr[ETH_ALEN] = { 0 };
+       u16 vid = 0;
        int ret;
 
-       if (!ds->drv->fdb_getnext)
+       if (!ds->drv->port_fdb_getnext)
                return -EOPNOTSUPP;
 
-       for (; ; idx++) {
+       for (;;) {
                bool is_static;
 
-               ret = ds->drv->fdb_getnext(ds, p->port, addr, &is_static);
+               ret = ds->drv->port_fdb_getnext(ds, p->port, addr, &vid,
+                                               &is_static);
                if (ret < 0)
                        break;
 
-               if (idx < cb->args[0])
-                       continue;
+               obj->u.fdb.addr = addr;
+               obj->u.fdb.vid = vid;
+               obj->u.fdb.ndm_state = is_static ? NUD_NOARP : NUD_REACHABLE;
 
-               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);
+               ret = obj->cb(dev, obj);
                if (ret < 0)
                        break;
        }
 
-       return idx;
+       return ret == -ENOENT ? 0 : ret;
 }
 
 static int dsa_slave_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
@@ -348,12 +458,17 @@ static int dsa_slave_stp_update(struct net_device *dev, u8 state)
 static int dsa_slave_port_attr_set(struct net_device *dev,
                                   struct switchdev_attr *attr)
 {
-       int ret = 0;
+       struct dsa_slave_priv *p = netdev_priv(dev);
+       struct dsa_switch *ds = p->parent;
+       int ret;
 
        switch (attr->id) {
        case SWITCHDEV_ATTR_PORT_STP_STATE:
-               if (attr->trans == SWITCHDEV_TRANS_COMMIT)
-                       ret = dsa_slave_stp_update(dev, attr->u.stp_state);
+               if (attr->trans == SWITCHDEV_TRANS_PREPARE)
+                       ret = ds->drv->port_stp_update ? 0 : -EOPNOTSUPP;
+               else
+                       ret = ds->drv->port_stp_update(ds, p->port,
+                                                      attr->u.stp_state);
                break;
        default:
                ret = -EOPNOTSUPP;
@@ -363,6 +478,71 @@ static int dsa_slave_port_attr_set(struct net_device *dev,
        return ret;
 }
 
+static int dsa_slave_port_obj_add(struct net_device *dev,
+                                 struct switchdev_obj *obj)
+{
+       int err;
+
+       /* For the prepare phase, ensure the full set of changes is feasable in
+        * one go in order to signal a failure properly. If an operation is not
+        * supported, return -EOPNOTSUPP.
+        */
+
+       switch (obj->id) {
+       case SWITCHDEV_OBJ_PORT_FDB:
+               err = dsa_slave_port_fdb_add(dev, obj);
+               break;
+       case SWITCHDEV_OBJ_PORT_VLAN:
+               err = dsa_slave_port_vlan_add(dev, obj);
+               break;
+       default:
+               err = -EOPNOTSUPP;
+               break;
+       }
+
+       return err;
+}
+
+static int dsa_slave_port_obj_del(struct net_device *dev,
+                                 struct switchdev_obj *obj)
+{
+       int err;
+
+       switch (obj->id) {
+       case SWITCHDEV_OBJ_PORT_FDB:
+               err = dsa_slave_port_fdb_del(dev, obj);
+               break;
+       case SWITCHDEV_OBJ_PORT_VLAN:
+               err = dsa_slave_port_vlan_del(dev, obj);
+               break;
+       default:
+               err = -EOPNOTSUPP;
+               break;
+       }
+
+       return err;
+}
+
+static int dsa_slave_port_obj_dump(struct net_device *dev,
+                                  struct switchdev_obj *obj)
+{
+       int err;
+
+       switch (obj->id) {
+       case SWITCHDEV_OBJ_PORT_FDB:
+               err = dsa_slave_port_fdb_dump(dev, obj);
+               break;
+       case SWITCHDEV_OBJ_PORT_VLAN:
+               err = dsa_slave_port_vlan_dump(dev, obj);
+               break;
+       default:
+               err = -EOPNOTSUPP;
+               break;
+       }
+
+       return err;
+}
+
 static int dsa_slave_bridge_port_join(struct net_device *dev,
                                      struct net_device *br)
 {
@@ -418,24 +598,53 @@ static int dsa_slave_port_attr_get(struct net_device *dev,
        return 0;
 }
 
-static netdev_tx_t dsa_slave_xmit(struct sk_buff *skb, struct net_device *dev)
+static inline netdev_tx_t dsa_netpoll_send_skb(struct dsa_slave_priv *p,
+                                              struct sk_buff *skb)
 {
-       struct dsa_slave_priv *p = netdev_priv(dev);
-
-       return p->xmit(skb, dev);
+#ifdef CONFIG_NET_POLL_CONTROLLER
+       if (p->netpoll)
+               netpoll_send_skb(p->netpoll, skb);
+#else
+       BUG();
+#endif
+       return NETDEV_TX_OK;
 }
 
-static netdev_tx_t dsa_slave_notag_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 sk_buff *nskb;
+
+       dev->stats.tx_packets++;
+       dev->stats.tx_bytes += skb->len;
 
-       skb->dev = p->parent->dst->master_netdev;
-       dev_queue_xmit(skb);
+       /* Transmit function may have to reallocate the original SKB */
+       nskb = p->xmit(skb, dev);
+       if (!nskb)
+               return NETDEV_TX_OK;
+
+       /* SKB for netpoll still need to be mangled with the protocol-specific
+        * tag to be successfully transmitted
+        */
+       if (unlikely(netpoll_tx_running(dev)))
+               return dsa_netpoll_send_skb(p, nskb);
+
+       /* Queue the SKB for transmission on the parent interface, but
+        * do not modify its EtherType
+        */
+       nskb->dev = p->parent->dst->master_netdev;
+       dev_queue_xmit(nskb);
 
        return NETDEV_TX_OK;
 }
 
+static struct sk_buff *dsa_slave_notag_xmit(struct sk_buff *skb,
+                                           struct net_device *dev)
+{
+       /* Just return the original SKB */
+       return skb;
+}
+
 
 /* ethtool operations *******************************************************/
 static int
@@ -665,6 +874,49 @@ static int dsa_slave_get_eee(struct net_device *dev, struct ethtool_eee *e)
        return ret;
 }
 
+#ifdef CONFIG_NET_POLL_CONTROLLER
+static int dsa_slave_netpoll_setup(struct net_device *dev,
+                                  struct netpoll_info *ni)
+{
+       struct dsa_slave_priv *p = netdev_priv(dev);
+       struct dsa_switch *ds = p->parent;
+       struct net_device *master = ds->dst->master_netdev;
+       struct netpoll *netpoll;
+       int err = 0;
+
+       netpoll = kzalloc(sizeof(*netpoll), GFP_KERNEL);
+       if (!netpoll)
+               return -ENOMEM;
+
+       err = __netpoll_setup(netpoll, master);
+       if (err) {
+               kfree(netpoll);
+               goto out;
+       }
+
+       p->netpoll = netpoll;
+out:
+       return err;
+}
+
+static void dsa_slave_netpoll_cleanup(struct net_device *dev)
+{
+       struct dsa_slave_priv *p = netdev_priv(dev);
+       struct netpoll *netpoll = p->netpoll;
+
+       if (!netpoll)
+               return;
+
+       p->netpoll = NULL;
+
+       __netpoll_free_async(netpoll);
+}
+
+static void dsa_slave_poll_controller(struct net_device *dev)
+{
+}
+#endif
+
 static const struct ethtool_ops dsa_slave_ethtool_ops = {
        .get_settings           = dsa_slave_get_settings,
        .set_settings           = dsa_slave_set_settings,
@@ -692,16 +944,27 @@ static const struct net_device_ops dsa_slave_netdev_ops = {
        .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_fdb_add            = switchdev_port_fdb_add,
+       .ndo_fdb_del            = switchdev_port_fdb_del,
+       .ndo_fdb_dump           = switchdev_port_fdb_dump,
        .ndo_do_ioctl           = dsa_slave_ioctl,
        .ndo_get_iflink         = dsa_slave_get_iflink,
+#ifdef CONFIG_NET_POLL_CONTROLLER
+       .ndo_netpoll_setup      = dsa_slave_netpoll_setup,
+       .ndo_netpoll_cleanup    = dsa_slave_netpoll_cleanup,
+       .ndo_poll_controller    = dsa_slave_poll_controller,
+#endif
+       .ndo_bridge_getlink     = switchdev_port_bridge_getlink,
+       .ndo_bridge_setlink     = switchdev_port_bridge_setlink,
+       .ndo_bridge_dellink     = switchdev_port_bridge_dellink,
 };
 
 static const struct switchdev_ops dsa_slave_switchdev_ops = {
        .switchdev_port_attr_get        = dsa_slave_port_attr_get,
        .switchdev_port_attr_set        = dsa_slave_port_attr_set,
+       .switchdev_port_obj_add         = dsa_slave_port_obj_add,
+       .switchdev_port_obj_del         = dsa_slave_port_obj_del,
+       .switchdev_port_obj_dump        = dsa_slave_port_obj_dump,
 };
 
 static void dsa_slave_adjust_link(struct net_device *dev)
@@ -889,7 +1152,7 @@ int dsa_slave_create(struct dsa_switch *ds, struct device *parent,
        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->priv_flags |= IFF_NO_QUEUE;
        slave_dev->netdev_ops = &dsa_slave_netdev_ops;
        slave_dev->switchdev_ops = &dsa_slave_switchdev_ops;