]> Git Repo - linux.git/blobdiff - net/switchdev/switchdev.c
Merge tag 'memblock-v5.18-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/rppt...
[linux.git] / net / switchdev / switchdev.c
index b62565278faccfb7f96228a19304a9ec6c161655..474f7638303390034143ea8bd28f6551dd44e251 100644 (file)
@@ -85,7 +85,7 @@ static int switchdev_deferred_enqueue(struct net_device *dev,
 {
        struct switchdev_deferred_item *dfitem;
 
-       dfitem = kmalloc(sizeof(*dfitem) + data_len, GFP_ATOMIC);
+       dfitem = kmalloc(struct_size(dfitem, data, data_len), GFP_ATOMIC);
        if (!dfitem)
                return -ENOMEM;
        dfitem->dev = dev;
@@ -408,6 +408,27 @@ static int switchdev_lower_dev_walk(struct net_device *lower_dev,
        return 0;
 }
 
+static struct net_device *
+switchdev_lower_dev_find_rcu(struct net_device *dev,
+                            bool (*check_cb)(const struct net_device *dev),
+                            bool (*foreign_dev_check_cb)(const struct net_device *dev,
+                                                         const struct net_device *foreign_dev))
+{
+       struct switchdev_nested_priv switchdev_priv = {
+               .check_cb = check_cb,
+               .foreign_dev_check_cb = foreign_dev_check_cb,
+               .dev = dev,
+               .lower_dev = NULL,
+       };
+       struct netdev_nested_priv priv = {
+               .data = &switchdev_priv,
+       };
+
+       netdev_walk_all_lower_dev_rcu(dev, switchdev_lower_dev_walk, &priv);
+
+       return switchdev_priv.lower_dev;
+}
+
 static struct net_device *
 switchdev_lower_dev_find(struct net_device *dev,
                         bool (*check_cb)(const struct net_device *dev),
@@ -424,7 +445,7 @@ switchdev_lower_dev_find(struct net_device *dev,
                .data = &switchdev_priv,
        };
 
-       netdev_walk_all_lower_dev_rcu(dev, switchdev_lower_dev_walk, &priv);
+       netdev_walk_all_lower_dev(dev, switchdev_lower_dev_walk, &priv);
 
        return switchdev_priv.lower_dev;
 }
@@ -437,63 +458,40 @@ static int __switchdev_handle_fdb_event_to_device(struct net_device *dev,
                                             const struct net_device *foreign_dev),
                int (*mod_cb)(struct net_device *dev, struct net_device *orig_dev,
                              unsigned long event, const void *ctx,
-                             const struct switchdev_notifier_fdb_info *fdb_info),
-               int (*lag_mod_cb)(struct net_device *dev, struct net_device *orig_dev,
-                                 unsigned long event, const void *ctx,
-                                 const struct switchdev_notifier_fdb_info *fdb_info))
+                             const struct switchdev_notifier_fdb_info *fdb_info))
 {
        const struct switchdev_notifier_info *info = &fdb_info->info;
-       struct net_device *br, *lower_dev;
+       struct net_device *br, *lower_dev, *switchdev;
        struct list_head *iter;
        int err = -EOPNOTSUPP;
 
        if (check_cb(dev))
                return mod_cb(dev, orig_dev, event, info->ctx, fdb_info);
 
-       if (netif_is_lag_master(dev)) {
-               if (!switchdev_lower_dev_find(dev, check_cb, foreign_dev_check_cb))
-                       goto maybe_bridged_with_us;
-
-               /* This is a LAG interface that we offload */
-               if (!lag_mod_cb)
-                       return -EOPNOTSUPP;
-
-               return lag_mod_cb(dev, orig_dev, event, info->ctx, fdb_info);
-       }
-
        /* Recurse through lower interfaces in case the FDB entry is pointing
-        * towards a bridge device.
+        * towards a bridge or a LAG device.
         */
-       if (netif_is_bridge_master(dev)) {
-               if (!switchdev_lower_dev_find(dev, check_cb, foreign_dev_check_cb))
-                       return 0;
-
-               /* This is a bridge interface that we offload */
-               netdev_for_each_lower_dev(dev, lower_dev, iter) {
-                       /* Do not propagate FDB entries across bridges */
-                       if (netif_is_bridge_master(lower_dev))
-                               continue;
-
-                       /* Bridge ports might be either us, or LAG interfaces
-                        * that we offload.
-                        */
-                       if (!check_cb(lower_dev) &&
-                           !switchdev_lower_dev_find(lower_dev, check_cb,
-                                                     foreign_dev_check_cb))
-                               continue;
-
-                       err = __switchdev_handle_fdb_event_to_device(lower_dev, orig_dev,
-                                                                    event, fdb_info, check_cb,
-                                                                    foreign_dev_check_cb,
-                                                                    mod_cb, lag_mod_cb);
-                       if (err && err != -EOPNOTSUPP)
-                               return err;
-               }
+       netdev_for_each_lower_dev(dev, lower_dev, iter) {
+               /* Do not propagate FDB entries across bridges */
+               if (netif_is_bridge_master(lower_dev))
+                       continue;
 
-               return 0;
+               /* Bridge ports might be either us, or LAG interfaces
+                * that we offload.
+                */
+               if (!check_cb(lower_dev) &&
+                   !switchdev_lower_dev_find_rcu(lower_dev, check_cb,
+                                                 foreign_dev_check_cb))
+                       continue;
+
+               err = __switchdev_handle_fdb_event_to_device(lower_dev, orig_dev,
+                                                            event, fdb_info, check_cb,
+                                                            foreign_dev_check_cb,
+                                                            mod_cb);
+               if (err && err != -EOPNOTSUPP)
+                       return err;
        }
 
-maybe_bridged_with_us:
        /* Event is neither on a bridge nor a LAG. Check whether it is on an
         * interface that is in a bridge with us.
         */
@@ -501,12 +499,16 @@ maybe_bridged_with_us:
        if (!br || !netif_is_bridge_master(br))
                return 0;
 
-       if (!switchdev_lower_dev_find(br, check_cb, foreign_dev_check_cb))
+       switchdev = switchdev_lower_dev_find_rcu(br, check_cb, foreign_dev_check_cb);
+       if (!switchdev)
                return 0;
 
+       if (!foreign_dev_check_cb(switchdev, dev))
+               return err;
+
        return __switchdev_handle_fdb_event_to_device(br, orig_dev, event, fdb_info,
                                                      check_cb, foreign_dev_check_cb,
-                                                     mod_cb, lag_mod_cb);
+                                                     mod_cb);
 }
 
 int switchdev_handle_fdb_event_to_device(struct net_device *dev, unsigned long event,
@@ -516,16 +518,13 @@ int switchdev_handle_fdb_event_to_device(struct net_device *dev, unsigned long e
                                             const struct net_device *foreign_dev),
                int (*mod_cb)(struct net_device *dev, struct net_device *orig_dev,
                              unsigned long event, const void *ctx,
-                             const struct switchdev_notifier_fdb_info *fdb_info),
-               int (*lag_mod_cb)(struct net_device *dev, struct net_device *orig_dev,
-                                 unsigned long event, const void *ctx,
-                                 const struct switchdev_notifier_fdb_info *fdb_info))
+                             const struct switchdev_notifier_fdb_info *fdb_info))
 {
        int err;
 
        err = __switchdev_handle_fdb_event_to_device(dev, dev, event, fdb_info,
                                                     check_cb, foreign_dev_check_cb,
-                                                    mod_cb, lag_mod_cb);
+                                                    mod_cb);
        if (err == -EOPNOTSUPP)
                err = 0;
 
@@ -536,13 +535,15 @@ EXPORT_SYMBOL_GPL(switchdev_handle_fdb_event_to_device);
 static int __switchdev_handle_port_obj_add(struct net_device *dev,
                        struct switchdev_notifier_port_obj_info *port_obj_info,
                        bool (*check_cb)(const struct net_device *dev),
+                       bool (*foreign_dev_check_cb)(const struct net_device *dev,
+                                                    const struct net_device *foreign_dev),
                        int (*add_cb)(struct net_device *dev, const void *ctx,
                                      const struct switchdev_obj *obj,
                                      struct netlink_ext_ack *extack))
 {
        struct switchdev_notifier_info *info = &port_obj_info->info;
+       struct net_device *br, *lower_dev, *switchdev;
        struct netlink_ext_ack *extack;
-       struct net_device *lower_dev;
        struct list_head *iter;
        int err = -EOPNOTSUPP;
 
@@ -566,15 +567,46 @@ static int __switchdev_handle_port_obj_add(struct net_device *dev,
                if (netif_is_bridge_master(lower_dev))
                        continue;
 
+               /* When searching for switchdev interfaces that are neighbors
+                * of foreign ones, and @dev is a bridge, do not recurse on the
+                * foreign interface again, it was already visited.
+                */
+               if (foreign_dev_check_cb && !check_cb(lower_dev) &&
+                   !switchdev_lower_dev_find(lower_dev, check_cb, foreign_dev_check_cb))
+                       continue;
+
                err = __switchdev_handle_port_obj_add(lower_dev, port_obj_info,
-                                                     check_cb, add_cb);
+                                                     check_cb, foreign_dev_check_cb,
+                                                     add_cb);
                if (err && err != -EOPNOTSUPP)
                        return err;
        }
 
-       return err;
+       /* Event is neither on a bridge nor a LAG. Check whether it is on an
+        * interface that is in a bridge with us.
+        */
+       if (!foreign_dev_check_cb)
+               return err;
+
+       br = netdev_master_upper_dev_get(dev);
+       if (!br || !netif_is_bridge_master(br))
+               return err;
+
+       switchdev = switchdev_lower_dev_find(br, check_cb, foreign_dev_check_cb);
+       if (!switchdev)
+               return err;
+
+       if (!foreign_dev_check_cb(switchdev, dev))
+               return err;
+
+       return __switchdev_handle_port_obj_add(br, port_obj_info, check_cb,
+                                              foreign_dev_check_cb, add_cb);
 }
 
+/* Pass through a port object addition, if @dev passes @check_cb, or replicate
+ * it towards all lower interfaces of @dev that pass @check_cb, if @dev is a
+ * bridge or a LAG.
+ */
 int switchdev_handle_port_obj_add(struct net_device *dev,
                        struct switchdev_notifier_port_obj_info *port_obj_info,
                        bool (*check_cb)(const struct net_device *dev),
@@ -585,21 +617,46 @@ int switchdev_handle_port_obj_add(struct net_device *dev,
        int err;
 
        err = __switchdev_handle_port_obj_add(dev, port_obj_info, check_cb,
-                                             add_cb);
+                                             NULL, add_cb);
        if (err == -EOPNOTSUPP)
                err = 0;
        return err;
 }
 EXPORT_SYMBOL_GPL(switchdev_handle_port_obj_add);
 
+/* Same as switchdev_handle_port_obj_add(), except if object is notified on a
+ * @dev that passes @foreign_dev_check_cb, it is replicated towards all devices
+ * that pass @check_cb and are in the same bridge as @dev.
+ */
+int switchdev_handle_port_obj_add_foreign(struct net_device *dev,
+                       struct switchdev_notifier_port_obj_info *port_obj_info,
+                       bool (*check_cb)(const struct net_device *dev),
+                       bool (*foreign_dev_check_cb)(const struct net_device *dev,
+                                                    const struct net_device *foreign_dev),
+                       int (*add_cb)(struct net_device *dev, const void *ctx,
+                                     const struct switchdev_obj *obj,
+                                     struct netlink_ext_ack *extack))
+{
+       int err;
+
+       err = __switchdev_handle_port_obj_add(dev, port_obj_info, check_cb,
+                                             foreign_dev_check_cb, add_cb);
+       if (err == -EOPNOTSUPP)
+               err = 0;
+       return err;
+}
+EXPORT_SYMBOL_GPL(switchdev_handle_port_obj_add_foreign);
+
 static int __switchdev_handle_port_obj_del(struct net_device *dev,
                        struct switchdev_notifier_port_obj_info *port_obj_info,
                        bool (*check_cb)(const struct net_device *dev),
+                       bool (*foreign_dev_check_cb)(const struct net_device *dev,
+                                                    const struct net_device *foreign_dev),
                        int (*del_cb)(struct net_device *dev, const void *ctx,
                                      const struct switchdev_obj *obj))
 {
        struct switchdev_notifier_info *info = &port_obj_info->info;
-       struct net_device *lower_dev;
+       struct net_device *br, *lower_dev, *switchdev;
        struct list_head *iter;
        int err = -EOPNOTSUPP;
 
@@ -621,15 +678,46 @@ static int __switchdev_handle_port_obj_del(struct net_device *dev,
                if (netif_is_bridge_master(lower_dev))
                        continue;
 
+               /* When searching for switchdev interfaces that are neighbors
+                * of foreign ones, and @dev is a bridge, do not recurse on the
+                * foreign interface again, it was already visited.
+                */
+               if (foreign_dev_check_cb && !check_cb(lower_dev) &&
+                   !switchdev_lower_dev_find(lower_dev, check_cb, foreign_dev_check_cb))
+                       continue;
+
                err = __switchdev_handle_port_obj_del(lower_dev, port_obj_info,
-                                                     check_cb, del_cb);
+                                                     check_cb, foreign_dev_check_cb,
+                                                     del_cb);
                if (err && err != -EOPNOTSUPP)
                        return err;
        }
 
-       return err;
+       /* Event is neither on a bridge nor a LAG. Check whether it is on an
+        * interface that is in a bridge with us.
+        */
+       if (!foreign_dev_check_cb)
+               return err;
+
+       br = netdev_master_upper_dev_get(dev);
+       if (!br || !netif_is_bridge_master(br))
+               return err;
+
+       switchdev = switchdev_lower_dev_find(br, check_cb, foreign_dev_check_cb);
+       if (!switchdev)
+               return err;
+
+       if (!foreign_dev_check_cb(switchdev, dev))
+               return err;
+
+       return __switchdev_handle_port_obj_del(br, port_obj_info, check_cb,
+                                              foreign_dev_check_cb, del_cb);
 }
 
+/* Pass through a port object deletion, if @dev passes @check_cb, or replicate
+ * it towards all lower interfaces of @dev that pass @check_cb, if @dev is a
+ * bridge or a LAG.
+ */
 int switchdev_handle_port_obj_del(struct net_device *dev,
                        struct switchdev_notifier_port_obj_info *port_obj_info,
                        bool (*check_cb)(const struct net_device *dev),
@@ -639,13 +727,35 @@ int switchdev_handle_port_obj_del(struct net_device *dev,
        int err;
 
        err = __switchdev_handle_port_obj_del(dev, port_obj_info, check_cb,
-                                             del_cb);
+                                             NULL, del_cb);
        if (err == -EOPNOTSUPP)
                err = 0;
        return err;
 }
 EXPORT_SYMBOL_GPL(switchdev_handle_port_obj_del);
 
+/* Same as switchdev_handle_port_obj_del(), except if object is notified on a
+ * @dev that passes @foreign_dev_check_cb, it is replicated towards all devices
+ * that pass @check_cb and are in the same bridge as @dev.
+ */
+int switchdev_handle_port_obj_del_foreign(struct net_device *dev,
+                       struct switchdev_notifier_port_obj_info *port_obj_info,
+                       bool (*check_cb)(const struct net_device *dev),
+                       bool (*foreign_dev_check_cb)(const struct net_device *dev,
+                                                    const struct net_device *foreign_dev),
+                       int (*del_cb)(struct net_device *dev, const void *ctx,
+                                     const struct switchdev_obj *obj))
+{
+       int err;
+
+       err = __switchdev_handle_port_obj_del(dev, port_obj_info, check_cb,
+                                             foreign_dev_check_cb, del_cb);
+       if (err == -EOPNOTSUPP)
+               err = 0;
+       return err;
+}
+EXPORT_SYMBOL_GPL(switchdev_handle_port_obj_del_foreign);
+
 static int __switchdev_handle_port_attr_set(struct net_device *dev,
                        struct switchdev_notifier_port_attr_info *port_attr_info,
                        bool (*check_cb)(const struct net_device *dev),
This page took 0.04699 seconds and 4 git commands to generate.