]> Git Repo - linux.git/blobdiff - net/switchdev/switchdev.c
bpf: prevent out of bounds speculation on pointer arithmetic
[linux.git] / net / switchdev / switchdev.c
index 74b9d916a58baaf3da7dae7172572ab2a88909eb..5df9d1138ac9026b03639d87b543a7cf5ea20905 100644 (file)
@@ -353,34 +353,35 @@ static size_t switchdev_obj_size(const struct switchdev_obj *obj)
        return 0;
 }
 
-static int __switchdev_port_obj_add(struct net_device *dev,
-                                   const struct switchdev_obj *obj,
-                                   struct switchdev_trans *trans)
+static int switchdev_port_obj_notify(enum switchdev_notifier_type nt,
+                                    struct net_device *dev,
+                                    const struct switchdev_obj *obj,
+                                    struct switchdev_trans *trans,
+                                    struct netlink_ext_ack *extack)
 {
-       const struct switchdev_ops *ops = dev->switchdev_ops;
-       struct net_device *lower_dev;
-       struct list_head *iter;
-       int err = -EOPNOTSUPP;
-
-       if (ops && ops->switchdev_port_obj_add)
-               return ops->switchdev_port_obj_add(dev, obj, trans);
+       int rc;
+       int err;
 
-       /* Switch device port(s) may be stacked under
-        * bond/team/vlan dev, so recurse down to add object on
-        * each port.
-        */
+       struct switchdev_notifier_port_obj_info obj_info = {
+               .obj = obj,
+               .trans = trans,
+               .handled = false,
+       };
 
-       netdev_for_each_lower_dev(dev, lower_dev, iter) {
-               err = __switchdev_port_obj_add(lower_dev, obj, trans);
-               if (err)
-                       break;
+       rc = call_switchdev_blocking_notifiers(nt, dev, &obj_info.info, extack);
+       err = notifier_to_errno(rc);
+       if (err) {
+               WARN_ON(!obj_info.handled);
+               return err;
        }
-
-       return err;
+       if (!obj_info.handled)
+               return -EOPNOTSUPP;
+       return 0;
 }
 
 static int switchdev_port_obj_add_now(struct net_device *dev,
-                                     const struct switchdev_obj *obj)
+                                     const struct switchdev_obj *obj,
+                                     struct netlink_ext_ack *extack)
 {
        struct switchdev_trans trans;
        int err;
@@ -397,7 +398,8 @@ static int switchdev_port_obj_add_now(struct net_device *dev,
         */
 
        trans.ph_prepare = true;
-       err = __switchdev_port_obj_add(dev, obj, &trans);
+       err = switchdev_port_obj_notify(SWITCHDEV_PORT_OBJ_ADD,
+                                       dev, obj, &trans, extack);
        if (err) {
                /* Prepare phase failed: abort the transaction.  Any
                 * resources reserved in the prepare phase are
@@ -416,7 +418,8 @@ static int switchdev_port_obj_add_now(struct net_device *dev,
         */
 
        trans.ph_prepare = false;
-       err = __switchdev_port_obj_add(dev, obj, &trans);
+       err = switchdev_port_obj_notify(SWITCHDEV_PORT_OBJ_ADD,
+                                       dev, obj, &trans, extack);
        WARN(err, "%s: Commit of object (id=%d) failed.\n", dev->name, obj->id);
        switchdev_trans_items_warn_destroy(dev, &trans);
 
@@ -429,7 +432,7 @@ static void switchdev_port_obj_add_deferred(struct net_device *dev,
        const struct switchdev_obj *obj = data;
        int err;
 
-       err = switchdev_port_obj_add_now(dev, obj);
+       err = switchdev_port_obj_add_now(dev, obj, NULL);
        if (err && err != -EOPNOTSUPP)
                netdev_err(dev, "failed (err=%d) to add object (id=%d)\n",
                           err, obj->id);
@@ -459,38 +462,21 @@ static int switchdev_port_obj_add_defer(struct net_device *dev,
  *     in case SWITCHDEV_F_DEFER flag is not set.
  */
 int switchdev_port_obj_add(struct net_device *dev,
-                          const struct switchdev_obj *obj)
+                          const struct switchdev_obj *obj,
+                          struct netlink_ext_ack *extack)
 {
        if (obj->flags & SWITCHDEV_F_DEFER)
                return switchdev_port_obj_add_defer(dev, obj);
        ASSERT_RTNL();
-       return switchdev_port_obj_add_now(dev, obj);
+       return switchdev_port_obj_add_now(dev, obj, extack);
 }
 EXPORT_SYMBOL_GPL(switchdev_port_obj_add);
 
 static int switchdev_port_obj_del_now(struct net_device *dev,
                                      const struct switchdev_obj *obj)
 {
-       const struct switchdev_ops *ops = dev->switchdev_ops;
-       struct net_device *lower_dev;
-       struct list_head *iter;
-       int err = -EOPNOTSUPP;
-
-       if (ops && ops->switchdev_port_obj_del)
-               return ops->switchdev_port_obj_del(dev, obj);
-
-       /* Switch device port(s) may be stacked under
-        * bond/team/vlan dev, so recurse down to delete object on
-        * each port.
-        */
-
-       netdev_for_each_lower_dev(dev, lower_dev, iter) {
-               err = switchdev_port_obj_del_now(lower_dev, obj);
-               if (err)
-                       break;
-       }
-
-       return err;
+       return switchdev_port_obj_notify(SWITCHDEV_PORT_OBJ_DEL,
+                                        dev, obj, NULL, NULL);
 }
 
 static void switchdev_port_obj_del_deferred(struct net_device *dev,
@@ -535,6 +521,7 @@ int switchdev_port_obj_del(struct net_device *dev,
 EXPORT_SYMBOL_GPL(switchdev_port_obj_del);
 
 static ATOMIC_NOTIFIER_HEAD(switchdev_notif_chain);
+static BLOCKING_NOTIFIER_HEAD(switchdev_blocking_notif_chain);
 
 /**
  *     register_switchdev_notifier - Register notifier
@@ -572,10 +559,38 @@ int call_switchdev_notifiers(unsigned long val, struct net_device *dev,
                             struct switchdev_notifier_info *info)
 {
        info->dev = dev;
+       info->extack = NULL;
        return atomic_notifier_call_chain(&switchdev_notif_chain, val, info);
 }
 EXPORT_SYMBOL_GPL(call_switchdev_notifiers);
 
+int register_switchdev_blocking_notifier(struct notifier_block *nb)
+{
+       struct blocking_notifier_head *chain = &switchdev_blocking_notif_chain;
+
+       return blocking_notifier_chain_register(chain, nb);
+}
+EXPORT_SYMBOL_GPL(register_switchdev_blocking_notifier);
+
+int unregister_switchdev_blocking_notifier(struct notifier_block *nb)
+{
+       struct blocking_notifier_head *chain = &switchdev_blocking_notif_chain;
+
+       return blocking_notifier_chain_unregister(chain, nb);
+}
+EXPORT_SYMBOL_GPL(unregister_switchdev_blocking_notifier);
+
+int call_switchdev_blocking_notifiers(unsigned long val, struct net_device *dev,
+                                     struct switchdev_notifier_info *info,
+                                     struct netlink_ext_ack *extack)
+{
+       info->dev = dev;
+       info->extack = extack;
+       return blocking_notifier_call_chain(&switchdev_blocking_notif_chain,
+                                           val, info);
+}
+EXPORT_SYMBOL_GPL(call_switchdev_blocking_notifiers);
+
 bool switchdev_port_same_parent_id(struct net_device *a,
                                   struct net_device *b)
 {
@@ -595,3 +610,109 @@ bool switchdev_port_same_parent_id(struct net_device *a,
        return netdev_phys_item_id_same(&a_attr.u.ppid, &b_attr.u.ppid);
 }
 EXPORT_SYMBOL_GPL(switchdev_port_same_parent_id);
+
+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),
+                       int (*add_cb)(struct net_device *dev,
+                                     const struct switchdev_obj *obj,
+                                     struct switchdev_trans *trans,
+                                     struct netlink_ext_ack *extack))
+{
+       struct netlink_ext_ack *extack;
+       struct net_device *lower_dev;
+       struct list_head *iter;
+       int err = -EOPNOTSUPP;
+
+       extack = switchdev_notifier_info_to_extack(&port_obj_info->info);
+
+       if (check_cb(dev)) {
+               /* This flag is only checked if the return value is success. */
+               port_obj_info->handled = true;
+               return add_cb(dev, port_obj_info->obj, port_obj_info->trans,
+                             extack);
+       }
+
+       /* Switch ports might be stacked under e.g. a LAG. Ignore the
+        * unsupported devices, another driver might be able to handle them. But
+        * propagate to the callers any hard errors.
+        *
+        * If the driver does its own bookkeeping of stacked ports, it's not
+        * necessary to go through this helper.
+        */
+       netdev_for_each_lower_dev(dev, lower_dev, iter) {
+               err = __switchdev_handle_port_obj_add(lower_dev, port_obj_info,
+                                                     check_cb, add_cb);
+               if (err && err != -EOPNOTSUPP)
+                       return err;
+       }
+
+       return err;
+}
+
+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),
+                       int (*add_cb)(struct net_device *dev,
+                                     const struct switchdev_obj *obj,
+                                     struct switchdev_trans *trans,
+                                     struct netlink_ext_ack *extack))
+{
+       int err;
+
+       err = __switchdev_handle_port_obj_add(dev, port_obj_info, check_cb,
+                                             add_cb);
+       if (err == -EOPNOTSUPP)
+               err = 0;
+       return err;
+}
+EXPORT_SYMBOL_GPL(switchdev_handle_port_obj_add);
+
+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),
+                       int (*del_cb)(struct net_device *dev,
+                                     const struct switchdev_obj *obj))
+{
+       struct net_device *lower_dev;
+       struct list_head *iter;
+       int err = -EOPNOTSUPP;
+
+       if (check_cb(dev)) {
+               /* This flag is only checked if the return value is success. */
+               port_obj_info->handled = true;
+               return del_cb(dev, port_obj_info->obj);
+       }
+
+       /* Switch ports might be stacked under e.g. a LAG. Ignore the
+        * unsupported devices, another driver might be able to handle them. But
+        * propagate to the callers any hard errors.
+        *
+        * If the driver does its own bookkeeping of stacked ports, it's not
+        * necessary to go through this helper.
+        */
+       netdev_for_each_lower_dev(dev, lower_dev, iter) {
+               err = __switchdev_handle_port_obj_del(lower_dev, port_obj_info,
+                                                     check_cb, del_cb);
+               if (err && err != -EOPNOTSUPP)
+                       return err;
+       }
+
+       return err;
+}
+
+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),
+                       int (*del_cb)(struct net_device *dev,
+                                     const struct switchdev_obj *obj))
+{
+       int err;
+
+       err = __switchdev_handle_port_obj_del(dev, port_obj_info, check_cb,
+                                             del_cb);
+       if (err == -EOPNOTSUPP)
+               err = 0;
+       return err;
+}
+EXPORT_SYMBOL_GPL(switchdev_handle_port_obj_del);
This page took 0.058189 seconds and 4 git commands to generate.