switchdev: allow caller to explicitly request attr_set as deferred
authorJiri Pirko <jiri@mellanox.com>
Wed, 14 Oct 2015 17:40:50 +0000 (19:40 +0200)
committerDavid S. Miller <davem@davemloft.net>
Thu, 15 Oct 2015 13:09:48 +0000 (06:09 -0700)
Caller should know if he can call attr_set directly (when holding RTNL)
or if he has to defer the att_set processing for later.

This also allows drivers to sleep inside attr_set and report operation
status back to switchdev core. Switchdev core then warns if status is
not ok, instead of silent errors happening in drivers.

Benefit from newly introduced switchdev deferred ops infrastructure.

Signed-off-by: Jiri Pirko <jiri@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/net/switchdev.h
net/bridge/br_stp.c
net/switchdev/switchdev.c

index d1c7f901ea61db447f70cbdf279c0e199af390c6..f7de6f8e9a4c857e92af46eed54d2acaf3f2300f 100644 (file)
@@ -17,6 +17,7 @@
 
 #define SWITCHDEV_F_NO_RECURSE         BIT(0)
 #define SWITCHDEV_F_SKIP_EOPNOTSUPP    BIT(1)
+#define SWITCHDEV_F_DEFER              BIT(2)
 
 struct switchdev_trans_item {
        struct list_head list;
index db6d243defb2dd0154887c273e4eb718ffab14b0..80c34d70218c0f9d2066016e3f5ba5fc56656490 100644 (file)
@@ -41,13 +41,14 @@ void br_set_state(struct net_bridge_port *p, unsigned int state)
 {
        struct switchdev_attr attr = {
                .id = SWITCHDEV_ATTR_ID_PORT_STP_STATE,
+               .flags = SWITCHDEV_F_DEFER,
                .u.stp_state = state,
        };
        int err;
 
        p->state = state;
        err = switchdev_port_attr_set(p->dev, &attr);
-       if (err && err != -EOPNOTSUPP)
+       if (err)
                br_warn(p->br, "error setting offload STP state on port %u(%s)\n",
                                (unsigned int) p->port_no, p->dev->name);
 }
index 23b4e5b347dca94958abc0e1d6e7e78f354b97da..007b8f40df069b0d9d0fda7fb9eb759fcbd86d97 100644 (file)
@@ -250,75 +250,12 @@ done:
        return err;
 }
 
-struct switchdev_attr_set_work {
-       struct work_struct work;
-       struct net_device *dev;
-       struct switchdev_attr attr;
-};
-
-static void switchdev_port_attr_set_work(struct work_struct *work)
-{
-       struct switchdev_attr_set_work *asw =
-               container_of(work, struct switchdev_attr_set_work, work);
-       int err;
-
-       rtnl_lock();
-       err = switchdev_port_attr_set(asw->dev, &asw->attr);
-       if (err && err != -EOPNOTSUPP)
-               netdev_err(asw->dev, "failed (err=%d) to set attribute (id=%d)\n",
-                          err, asw->attr.id);
-       rtnl_unlock();
-
-       dev_put(asw->dev);
-       kfree(work);
-}
-
-static int switchdev_port_attr_set_defer(struct net_device *dev,
-                                        const struct switchdev_attr *attr)
-{
-       struct switchdev_attr_set_work *asw;
-
-       asw = kmalloc(sizeof(*asw), GFP_ATOMIC);
-       if (!asw)
-               return -ENOMEM;
-
-       INIT_WORK(&asw->work, switchdev_port_attr_set_work);
-
-       dev_hold(dev);
-       asw->dev = dev;
-       memcpy(&asw->attr, attr, sizeof(asw->attr));
-
-       schedule_work(&asw->work);
-
-       return 0;
-}
-
-/**
- *     switchdev_port_attr_set - Set port attribute
- *
- *     @dev: port device
- *     @attr: attribute to set
- *
- *     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.
- */
-int switchdev_port_attr_set(struct net_device *dev,
-                           const struct switchdev_attr *attr)
+static int switchdev_port_attr_set_now(struct net_device *dev,
+                                      const struct switchdev_attr *attr)
 {
        struct switchdev_trans trans;
        int err;
 
-       if (!rtnl_is_locked()) {
-               /* Running prepare-commit transaction across stacked
-                * devices requires nothing moves, so if rtnl_lock is
-                * not held, schedule a worker thread to hold rtnl_lock
-                * while setting attr.
-                */
-
-               return switchdev_port_attr_set_defer(dev, attr);
-       }
-
        switchdev_trans_init(&trans);
 
        /* Phase I: prepare for attr set. Driver/device should fail
@@ -355,6 +292,47 @@ int switchdev_port_attr_set(struct net_device *dev,
 
        return err;
 }
+
+static void switchdev_port_attr_set_deferred(struct net_device *dev,
+                                            const void *data)
+{
+       const struct switchdev_attr *attr = data;
+       int err;
+
+       err = switchdev_port_attr_set_now(dev, attr);
+       if (err && err != -EOPNOTSUPP)
+               netdev_err(dev, "failed (err=%d) to set attribute (id=%d)\n",
+                          err, attr->id);
+}
+
+static int switchdev_port_attr_set_defer(struct net_device *dev,
+                                        const struct switchdev_attr *attr)
+{
+       return switchdev_deferred_enqueue(dev, attr, sizeof(*attr),
+                                         switchdev_port_attr_set_deferred);
+}
+
+/**
+ *     switchdev_port_attr_set - Set port attribute
+ *
+ *     @dev: port device
+ *     @attr: attribute to set
+ *
+ *     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_attr_set(struct net_device *dev,
+                           const struct switchdev_attr *attr)
+{
+       if (attr->flags & SWITCHDEV_F_DEFER)
+               return switchdev_port_attr_set_defer(dev, attr);
+       ASSERT_RTNL();
+       return switchdev_port_attr_set_now(dev, attr);
+}
 EXPORT_SYMBOL_GPL(switchdev_port_attr_set);
 
 static int __switchdev_port_obj_add(struct net_device *dev,