@@ -66,6 +66,7 @@ enum switchdev_obj_id {
struct switchdev_obj {
enum switchdev_obj_id id;
+ u32 flags;
};
/* SWITCHDEV_OBJ_ID_PORT_VLAN */
@@ -304,21 +304,8 @@ static int __switchdev_port_obj_add(struct net_device *dev,
return err;
}
-/**
- * switchdev_port_obj_add - Add port object
- *
- * @dev: port device
- * @id: object ID
- * @obj: object to add
- *
- * Use a 2-phase prepare-commit transaction model to ensure
- * system is not left in a partially updated state due to
- * failure from driver/device.
- *
- * rtnl_lock must be held.
- */
-int switchdev_port_obj_add(struct net_device *dev,
- const struct switchdev_obj *obj)
+static int switchdev_port_obj_add_now(struct net_device *dev,
+ const struct switchdev_obj *obj)
{
struct switchdev_trans trans;
int err;
@@ -360,17 +347,9 @@ int switchdev_port_obj_add(struct net_device *dev,
return err;
}
-EXPORT_SYMBOL_GPL(switchdev_port_obj_add);
-/**
- * switchdev_port_obj_del - Delete port object
- *
- * @dev: port device
- * @id: object ID
- * @obj: object to delete
- */
-int switchdev_port_obj_del(struct net_device *dev,
- const struct switchdev_obj *obj)
+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;
@@ -386,13 +365,119 @@ int switchdev_port_obj_del(struct net_device *dev,
*/
netdev_for_each_lower_dev(dev, lower_dev, iter) {
- err = switchdev_port_obj_del(lower_dev, obj);
+ err = switchdev_port_obj_del_now(lower_dev, obj);
if (err)
break;
}
return err;
}
+
+struct switchdev_obj_work {
+ struct work_struct work;
+ struct net_device *dev;
+ struct switchdev_obj obj;
+ bool add; /* add of del */
+};
+
+static void switchdev_port_obj_work(struct work_struct *work)
+{
+ struct switchdev_obj_work *ow =
+ container_of(work, struct switchdev_obj_work, work);
+ bool rtnl_locked = rtnl_is_locked();
+ int err;
+
+ if (!rtnl_locked)
+ rtnl_lock();
+ if (ow->add)
+ err = switchdev_port_obj_add_now(ow->dev, &ow->obj);
+ else
+ err = switchdev_port_obj_del_now(ow->dev, &ow->obj);
+ if (err && err != -EOPNOTSUPP)
+ netdev_err(ow->dev, "failed (err=%d) to %s object (id=%d)\n",
+ err, ow->add ? "add" : "del", ow->obj.id);
+ if (!rtnl_locked)
+ rtnl_unlock();
+
+ dev_put(ow->dev);
+ kfree(ow);
+}
+
+static int switchdev_port_obj_work_schedule(struct net_device *dev,
+ const struct switchdev_obj *obj,
+ bool add)
+{
+ struct switchdev_obj_work *ow;
+
+ ow = kmalloc(sizeof(*ow), GFP_ATOMIC);
+ if (!ow)
+ return -ENOMEM;
+
+ INIT_WORK(&ow->work, switchdev_port_obj_work);
+
+ dev_hold(dev);
+ ow->dev = dev;
+ memcpy(&ow->obj, obj, sizeof(ow->obj));
+ ow->add = add;
+
+ queue_work(switchdev_wq, &ow->work);
+ return 0;
+}
+
+static int switchdev_port_obj_add_defer(struct net_device *dev,
+ const struct switchdev_obj *obj)
+{
+ return switchdev_port_obj_work_schedule(dev, obj, true);
+}
+
+/**
+ * switchdev_port_obj_add - Add port object
+ *
+ * @dev: port device
+ * @id: object ID
+ * @obj: object to add
+ *
+ * Use a 2-phase prepare-commit transaction model to ensure
+ * system is not left in a partially updated state due to
+ * failure from driver/device.
+ *
+ * rtnl_lock must be held and must not be in atomic section,
+ * in case SWITCHDEV_F_DEFER flag is not set.
+ */
+int switchdev_port_obj_add(struct net_device *dev,
+ const struct switchdev_obj *obj)
+{
+ if (obj->flags & SWITCHDEV_F_DEFER)
+ return switchdev_port_obj_add_defer(dev, obj);
+ ASSERT_RTNL();
+ return switchdev_port_obj_add_now(dev, obj);
+}
+EXPORT_SYMBOL_GPL(switchdev_port_obj_add);
+
+static int switchdev_port_obj_del_defer(struct net_device *dev,
+ const struct switchdev_obj *obj)
+{
+ return switchdev_port_obj_work_schedule(dev, obj, false);
+}
+
+/**
+ * switchdev_port_obj_del - Delete port object
+ *
+ * @dev: port device
+ * @id: object ID
+ * @obj: object to delete
+ *
+ * rtnl_lock must be held and must not be in atomic section,
+ * in case SWITCHDEV_F_DEFER flag is not set.
+ */
+int switchdev_port_obj_del(struct net_device *dev,
+ const struct switchdev_obj *obj)
+{
+ if (obj->flags & SWITCHDEV_F_DEFER)
+ return switchdev_port_obj_del_defer(dev, obj);
+ ASSERT_RTNL();
+ return switchdev_port_obj_del_now(dev, obj);
+}
EXPORT_SYMBOL_GPL(switchdev_port_obj_del);
/**