net: dsa: Add support for learning FDB through notification
authorArkadi Sharshevsky <arkadis@mellanox.com>
Sun, 6 Aug 2017 13:15:42 +0000 (16:15 +0300)
committerDavid S. Miller <davem@davemloft.net>
Mon, 7 Aug 2017 21:48:48 +0000 (14:48 -0700)
Add support for learning FDB through notification. The driver defers
the hardware update via ordered work queue. In case of a successful
FDB add a notification is sent back to bridge.

In case of hw FDB del failure the static FDB will be deleted from
the bridge, thus, the interface is moved to down state in order to
indicate inconsistent situation.

Signed-off-by: Arkadi Sharshevsky <arkadis@mellanox.com>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
net/dsa/dsa.c
net/dsa/dsa_priv.h
net/dsa/slave.c

index 4118848b8e58728d1bf8d471a755aef051cb6fcd..99e38af85fc5f75f720a5639358755df58e9d204 100644 (file)
@@ -282,10 +282,22 @@ static struct packet_type dsa_pack_type __read_mostly = {
        .func   = dsa_switch_rcv,
 };
 
+static struct workqueue_struct *dsa_owq;
+
+bool dsa_schedule_work(struct work_struct *work)
+{
+       return queue_work(dsa_owq, work);
+}
+
 static int __init dsa_init_module(void)
 {
        int rc;
 
+       dsa_owq = alloc_ordered_workqueue("dsa_ordered",
+                                         WQ_MEM_RECLAIM);
+       if (!dsa_owq)
+               return -ENOMEM;
+
        rc = dsa_slave_register_notifier();
        if (rc)
                return rc;
@@ -305,6 +317,7 @@ static void __exit dsa_cleanup_module(void)
        dsa_slave_unregister_notifier();
        dev_remove_pack(&dsa_pack_type);
        dsa_legacy_unregister();
+       destroy_workqueue(dsa_owq);
 }
 module_exit(dsa_cleanup_module);
 
index c0ee6a7694f8bce7387d1340c1476ffc7d22efb2..fe90e64752972be16d0c6ad72b9df68bae26ef3a 100644 (file)
@@ -106,6 +106,7 @@ void dsa_cpu_dsa_destroy(struct dsa_port *dport);
 const struct dsa_device_ops *dsa_resolve_tag_protocol(int tag_protocol);
 int dsa_cpu_port_ethtool_setup(struct dsa_port *cpu_dp);
 void dsa_cpu_port_ethtool_restore(struct dsa_port *cpu_dp);
+bool dsa_schedule_work(struct work_struct *work);
 
 /* legacy.c */
 int dsa_legacy_register(void);
index 6a1d4d6d212b5d7d1d04b2cc892f9d7eea7847bf..064f833a04222ea7a368e3100b6d02971f488b2f 100644 (file)
@@ -1332,19 +1332,142 @@ static int dsa_slave_netdevice_event(struct notifier_block *nb,
        return NOTIFY_DONE;
 }
 
+struct dsa_switchdev_event_work {
+       struct work_struct work;
+       struct switchdev_notifier_fdb_info fdb_info;
+       struct net_device *dev;
+       unsigned long event;
+};
+
+static void dsa_slave_switchdev_event_work(struct work_struct *work)
+{
+       struct dsa_switchdev_event_work *switchdev_work =
+               container_of(work, struct dsa_switchdev_event_work, work);
+       struct net_device *dev = switchdev_work->dev;
+       struct switchdev_notifier_fdb_info *fdb_info;
+       struct dsa_slave_priv *p = netdev_priv(dev);
+       int err;
+
+       rtnl_lock();
+       switch (switchdev_work->event) {
+       case SWITCHDEV_FDB_ADD_TO_DEVICE:
+               fdb_info = &switchdev_work->fdb_info;
+               err = dsa_port_fdb_add(p->dp, fdb_info->addr, fdb_info->vid);
+               if (err) {
+                       netdev_dbg(dev, "fdb add failed err=%d\n", err);
+                       break;
+               }
+               call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED, dev,
+                                        &fdb_info->info);
+               break;
+
+       case SWITCHDEV_FDB_DEL_TO_DEVICE:
+               fdb_info = &switchdev_work->fdb_info;
+               err = dsa_port_fdb_del(p->dp, fdb_info->addr, fdb_info->vid);
+               if (err) {
+                       netdev_dbg(dev, "fdb del failed err=%d\n", err);
+                       dev_close(dev);
+               }
+               break;
+       }
+       rtnl_unlock();
+
+       kfree(switchdev_work->fdb_info.addr);
+       kfree(switchdev_work);
+       dev_put(dev);
+}
+
+static int
+dsa_slave_switchdev_fdb_work_init(struct dsa_switchdev_event_work *
+                                 switchdev_work,
+                                 const struct switchdev_notifier_fdb_info *
+                                 fdb_info)
+{
+       memcpy(&switchdev_work->fdb_info, fdb_info,
+              sizeof(switchdev_work->fdb_info));
+       switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC);
+       if (!switchdev_work->fdb_info.addr)
+               return -ENOMEM;
+       ether_addr_copy((u8 *)switchdev_work->fdb_info.addr,
+                       fdb_info->addr);
+       return 0;
+}
+
+/* Called under rcu_read_lock() */
+static int dsa_slave_switchdev_event(struct notifier_block *unused,
+                                    unsigned long event, void *ptr)
+{
+       struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
+       struct dsa_switchdev_event_work *switchdev_work;
+
+       if (!dsa_slave_dev_check(dev))
+               return NOTIFY_DONE;
+
+       switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC);
+       if (!switchdev_work)
+               return NOTIFY_BAD;
+
+       INIT_WORK(&switchdev_work->work,
+                 dsa_slave_switchdev_event_work);
+       switchdev_work->dev = dev;
+       switchdev_work->event = event;
+
+       switch (event) {
+       case SWITCHDEV_FDB_ADD_TO_DEVICE: /* fall through */
+       case SWITCHDEV_FDB_DEL_TO_DEVICE:
+               if (dsa_slave_switchdev_fdb_work_init(switchdev_work,
+                                                     ptr))
+                       goto err_fdb_work_init;
+               dev_hold(dev);
+               break;
+       default:
+               kfree(switchdev_work);
+               return NOTIFY_DONE;
+       }
+
+       dsa_schedule_work(&switchdev_work->work);
+       return NOTIFY_OK;
+
+err_fdb_work_init:
+       kfree(switchdev_work);
+       return NOTIFY_BAD;
+}
+
 static struct notifier_block dsa_slave_nb __read_mostly = {
-       .notifier_call  = dsa_slave_netdevice_event,
+       .notifier_call  = dsa_slave_netdevice_event,
+};
+
+static struct notifier_block dsa_slave_switchdev_notifier = {
+       .notifier_call = dsa_slave_switchdev_event,
 };
 
 int dsa_slave_register_notifier(void)
 {
-       return register_netdevice_notifier(&dsa_slave_nb);
+       int err;
+
+       err = register_netdevice_notifier(&dsa_slave_nb);
+       if (err)
+               return err;
+
+       err = register_switchdev_notifier(&dsa_slave_switchdev_notifier);
+       if (err)
+               goto err_switchdev_nb;
+
+       return 0;
+
+err_switchdev_nb:
+       unregister_netdevice_notifier(&dsa_slave_nb);
+       return err;
 }
 
 void dsa_slave_unregister_notifier(void)
 {
        int err;
 
+       err = unregister_switchdev_notifier(&dsa_slave_switchdev_notifier);
+       if (err)
+               pr_err("DSA: failed to unregister switchdev notifier (%d)\n", err);
+
        err = unregister_netdevice_notifier(&dsa_slave_nb);
        if (err)
                pr_err("DSA: failed to unregister slave notifier (%d)\n", err);