Allow drivers to register netdev callbacks for tc offload in linux bonds.
If a netdev has registered and is a slave of a given bond, then any tc
rules offloaded to the bond will be relayed to it if both the bond and the
slave permit hw offload.

Because the bond itself is not offloaded, just the rules, we don't care
about whether the bond ports are on the same device or whether some of
slaves are representor ports and some are not.

Signed-off-by: John Hurley <john.hur...@netronome.com>
---
 drivers/net/bonding/bond_main.c | 195 +++++++++++++++++++++++++++++++++++++++-
 include/net/bonding.h           |   7 ++
 2 files changed, 201 insertions(+), 1 deletion(-)

diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c
index e6415f6..d9e41cf 100644
--- a/drivers/net/bonding/bond_main.c
+++ b/drivers/net/bonding/bond_main.c
@@ -335,9 +335,201 @@ static inline unsigned int bond_get_offload_cnt(struct 
bonding *bond)
        return bond->tc_block->offloadcnt;
 }
 
+struct tcf_bond_cb {
+       struct list_head list;
+       tc_setup_cb_t *cb;
+       void *cb_priv;
+};
+
+struct tcf_bond_off {
+       struct rhash_head ht_node;
+       const struct net_device *netdev;
+       unsigned int refcnt;
+       struct list_head cb_list;
+};
+
+static const struct rhashtable_params tcf_bond_ht_params = {
+       .key_offset = offsetof(struct tcf_bond_off, netdev),
+       .head_offset = offsetof(struct tcf_bond_off, ht_node),
+       .key_len = sizeof(const struct net_device *),
+};
+
+static struct tcf_bond_off *tcf_bond_off_lookup(const struct net_device *dev)
+{
+       struct bond_net *bn = net_generic(dev_net(dev), bond_net_id);
+
+       return rhashtable_lookup_fast(&bn->bond_offload_ht, &dev,
+                                     tcf_bond_ht_params);
+}
+
+static struct tcf_bond_cb *tcf_bond_off_cb_lookup(struct tcf_bond_off *off,
+                                                 tc_setup_cb_t *cb,
+                                                 void *cb_priv)
+{
+       struct tcf_bond_cb *bond_cb;
+
+       list_for_each_entry(bond_cb, &off->cb_list, list)
+               if (bond_cb->cb == cb && bond_cb->cb_priv == cb_priv)
+                       return bond_cb;
+       return NULL;
+}
+
+static struct tcf_bond_off *tcf_bond_off_get(const struct net_device *dev,
+                                            tc_setup_cb_t *cb,
+                                            void *cb_priv)
+{
+       struct tcf_bond_off *bond_off;
+       struct bond_net *bn;
+
+       bond_off = tcf_bond_off_lookup(dev);
+       if (bond_off)
+               goto inc_ref;
+
+       bond_off = kzalloc(sizeof(*bond_off), GFP_KERNEL);
+       if (!bond_off)
+               return NULL;
+       INIT_LIST_HEAD(&bond_off->cb_list);
+       bond_off->netdev = dev;
+       bn = net_generic(dev_net(dev), bond_net_id);
+       rhashtable_insert_fast(&bn->bond_offload_ht, &bond_off->ht_node,
+                              tcf_bond_ht_params);
+
+inc_ref:
+       bond_off->refcnt++;
+       return bond_off;
+}
+
+static void tcf_bond_off_put(struct tcf_bond_off *bond_off)
+{
+       struct bond_net *bn;
+
+       if (--bond_off->refcnt)
+               return;
+       bn = net_generic(dev_net(bond_off->netdev), bond_net_id);
+       rhashtable_remove_fast(&bn->bond_offload_ht, &bond_off->ht_node,
+                              tcf_bond_ht_params);
+       kfree(bond_off);
+}
+
+static int tcf_bond_off_cb_add(struct tcf_bond_off *bond_off,
+                              tc_setup_cb_t *cb, void *cb_priv)
+{
+       struct tcf_bond_cb *bond_cb;
+
+       bond_cb = tcf_bond_off_cb_lookup(bond_off, cb, cb_priv);
+       if (WARN_ON(bond_cb))
+               return -EEXIST;
+       bond_cb = kzalloc(sizeof(*bond_cb), GFP_KERNEL);
+       if (!bond_cb)
+               return -ENOMEM;
+       bond_cb->cb = cb;
+       bond_cb->cb_priv = cb_priv;
+       list_add(&bond_cb->list, &bond_off->cb_list);
+       return 0;
+}
+
+static void tcf_bond_off_cb_del(struct tcf_bond_off *bond_off,
+                               tc_setup_cb_t *cb, void *cb_priv)
+{
+       struct tcf_bond_cb *bond_cb;
+
+       bond_cb = tcf_bond_off_cb_lookup(bond_off, cb, cb_priv);
+       if (WARN_ON(!bond_cb))
+               return;
+       list_del(&bond_cb->list);
+       kfree(bond_cb);
+}
+
+static int __tc_setup_cb_bond_register(const struct net_device *dev,
+                                      tc_setup_cb_t *cb, void *cb_priv)
+{
+       struct tcf_bond_off *bond_off = tcf_bond_off_get(dev, cb, cb_priv);
+       int err;
+
+       if (!bond_off)
+               return -ENOMEM;
+       err = tcf_bond_off_cb_add(bond_off, cb, cb_priv);
+       if (err)
+               goto err_cb_add;
+       return 0;
+
+err_cb_add:
+       tcf_bond_off_put(bond_off);
+       return err;
+}
+
+int tc_setup_cb_bond_register(const struct net_device *dev, tc_setup_cb_t *cb,
+                             void *cb_priv)
+{
+       int err;
+
+       rtnl_lock();
+       err = __tc_setup_cb_bond_register(dev, cb, cb_priv);
+       rtnl_unlock();
+       return err;
+}
+EXPORT_SYMBOL_GPL(tc_setup_cb_bond_register);
+
+static void __tc_setup_cb_bond_unregister(const struct net_device *dev,
+                                         tc_setup_cb_t *cb, void *cb_priv)
+{
+       struct tcf_bond_off *bond_off = tcf_bond_off_lookup(dev);
+
+       if (WARN_ON(!bond_off))
+               return;
+       tcf_bond_off_cb_del(bond_off, cb, cb_priv);
+       tcf_bond_off_put(bond_off);
+}
+
+void tc_setup_cb_bond_unregister(const struct net_device *dev,
+                                tc_setup_cb_t *cb, void *cb_priv)
+{
+       rtnl_lock();
+       __tc_setup_cb_bond_unregister(dev, cb, cb_priv);
+       rtnl_unlock();
+}
+EXPORT_SYMBOL_GPL(tc_setup_cb_bond_unregister);
+
 static int bond_tc_relay_cb(enum tc_setup_type type, void *type_data,
                            void *cb_priv)
 {
+       struct net_device *bond_dev = cb_priv;
+       struct tcf_bond_off *bond_off;
+       struct tcf_bond_cb *bond_cb;
+       struct list_head *iter;
+       struct bonding *bond;
+       struct slave *slave;
+       int err;
+
+       bond = netdev_priv(bond_dev);
+
+       if (!tc_can_offload(bond_dev))
+               return -EOPNOTSUPP;
+
+       bond_for_each_slave(bond, slave, iter) {
+               if (!tc_can_offload(slave->dev))
+                       continue;
+
+               bond_off = tcf_bond_off_lookup(slave->dev);
+               if (!bond_off)
+                       continue;
+
+               list_for_each_entry(bond_cb, &bond_off->cb_list, list) {
+                       err = bond_cb->cb(type, type_data, bond_cb->cb_priv);
+                       /* Possible here that some of the relayed callbacks are
+                        * accepted before the error meaning a rule add may be
+                        * offloaded to some ports and not others.
+                        *
+                        * If skip_sw is set then the classifier will generate
+                        * a destroy message undoing the adds. If not set then
+                        * some of the relays exist in hw and some software
+                        * only.
+                        */
+                       if (err)
+                               return err;
+               }
+       }
+
        return 0;
 }
 
@@ -4829,7 +5021,7 @@ static int __net_init bond_net_init(struct net *net)
        bond_create_proc_dir(bn);
        bond_create_sysfs(bn);
 
-       return 0;
+       return rhashtable_init(&bn->bond_offload_ht, &tcf_bond_ht_params);
 }
 
 static void __net_exit bond_net_exit(struct net *net)
@@ -4848,6 +5040,7 @@ static void __net_exit bond_net_exit(struct net *net)
        rtnl_unlock();
 
        bond_destroy_proc_dir(bn);
+       rhashtable_destroy(&bn->bond_offload_ht);
 }
 
 static struct pernet_operations bond_net_ops = {
diff --git a/include/net/bonding.h b/include/net/bonding.h
index 424b9ea..056f5fc 100644
--- a/include/net/bonding.h
+++ b/include/net/bonding.h
@@ -30,6 +30,7 @@
 #include <net/bond_alb.h>
 #include <net/bond_options.h>
 #include <net/pkt_cls.h>
+#include <net/act_api.h>
 
 #define BOND_MAX_ARP_TARGETS   16
 
@@ -584,6 +585,7 @@ struct bond_net {
        struct proc_dir_entry   *proc_dir;
 #endif
        struct class_attribute  class_attr_bonding_masters;
+       struct rhashtable       bond_offload_ht;
 };
 
 int bond_arp_rcv(const struct sk_buff *skb, struct bonding *bond, struct slave 
*slave);
@@ -620,6 +622,11 @@ int bond_update_slave_arr(struct bonding *bond, struct 
slave *skipslave);
 void bond_slave_arr_work_rearm(struct bonding *bond, unsigned long delay);
 void bond_work_init_all(struct bonding *bond);
 
+int tc_setup_cb_bond_register(const struct net_device *dev, tc_setup_cb_t *cb,
+                             void *cb_priv);
+void tc_setup_cb_bond_unregister(const struct net_device *dev,
+                                tc_setup_cb_t *cb, void *cb_priv);
+
 #ifdef CONFIG_PROC_FS
 void bond_create_proc_entry(struct bonding *bond);
 void bond_remove_proc_entry(struct bonding *bond);
-- 
2.7.4

Reply via email to