Merge remote-tracking branch 'net-next/master'
[deliverable/linux.git] / net / batman-adv / sysfs.c
index fe9ca94ddee2a2da3ce18baace7487c3db54ba53..02d96f224c602cb4568e3357199227ef668f9a23 100644 (file)
@@ -37,6 +37,7 @@
 #include <linux/stddef.h>
 #include <linux/string.h>
 #include <linux/stringify.h>
+#include <linux/workqueue.h>
 
 #include "bridge_loop_avoidance.h"
 #include "distributed-arp-table.h"
@@ -428,6 +429,13 @@ static ssize_t batadv_show_gw_mode(struct kobject *kobj, struct attribute *attr,
        struct batadv_priv *bat_priv = batadv_kobj_to_batpriv(kobj);
        int bytes_written;
 
+       /* GW mode is not available if the routing algorithm in use does not
+        * implement the GW API
+        */
+       if (!bat_priv->algo_ops->gw.get_best_gw_node ||
+           !bat_priv->algo_ops->gw.is_eligible)
+               return -ENOENT;
+
        switch (atomic_read(&bat_priv->gw.mode)) {
        case BATADV_GW_MODE_CLIENT:
                bytes_written = sprintf(buff, "%s\n",
@@ -455,6 +463,13 @@ static ssize_t batadv_store_gw_mode(struct kobject *kobj,
        char *curr_gw_mode_str;
        int gw_mode_tmp = -1;
 
+       /* toggling GW mode is allowed only if the routing algorithm in use
+        * provides the GW API
+        */
+       if (!bat_priv->algo_ops->gw.get_best_gw_node ||
+           !bat_priv->algo_ops->gw.is_eligible)
+               return -EINVAL;
+
        if (buff[count - 1] == '\n')
                buff[count - 1] = '\0';
 
@@ -514,6 +529,50 @@ static ssize_t batadv_store_gw_mode(struct kobject *kobj,
        return count;
 }
 
+static ssize_t batadv_show_gw_sel_class(struct kobject *kobj,
+                                       struct attribute *attr, char *buff)
+{
+       struct batadv_priv *bat_priv = batadv_kobj_to_batpriv(kobj);
+
+       /* GW selection class is not available if the routing algorithm in use
+        * does not implement the GW API
+        */
+       if (!bat_priv->algo_ops->gw.get_best_gw_node ||
+           !bat_priv->algo_ops->gw.is_eligible)
+               return -ENOENT;
+
+       if (bat_priv->algo_ops->gw.show_sel_class)
+               return bat_priv->algo_ops->gw.show_sel_class(bat_priv, buff);
+
+       return sprintf(buff, "%i\n", atomic_read(&bat_priv->gw.sel_class));
+}
+
+static ssize_t batadv_store_gw_sel_class(struct kobject *kobj,
+                                        struct attribute *attr, char *buff,
+                                        size_t count)
+{
+       struct batadv_priv *bat_priv = batadv_kobj_to_batpriv(kobj);
+
+       /* setting the GW selection class is allowed only if the routing
+        * algorithm in use implements the GW API
+        */
+       if (!bat_priv->algo_ops->gw.get_best_gw_node ||
+           !bat_priv->algo_ops->gw.is_eligible)
+               return -EINVAL;
+
+       if (buff[count - 1] == '\n')
+               buff[count - 1] = '\0';
+
+       if (bat_priv->algo_ops->gw.store_sel_class)
+               return bat_priv->algo_ops->gw.store_sel_class(bat_priv, buff,
+                                                             count);
+
+       return __batadv_store_uint_attr(buff, count, 1, BATADV_TQ_MAX_VALUE,
+                                       batadv_post_gw_reselect, attr,
+                                       &bat_priv->gw.sel_class,
+                                       bat_priv->soft_iface);
+}
+
 static ssize_t batadv_show_gw_bwidth(struct kobject *kobj,
                                     struct attribute *attr, char *buff)
 {
@@ -625,8 +684,8 @@ BATADV_ATTR_SIF_UINT(orig_interval, orig_interval, S_IRUGO | S_IWUSR,
                     2 * BATADV_JITTER, INT_MAX, NULL);
 BATADV_ATTR_SIF_UINT(hop_penalty, hop_penalty, S_IRUGO | S_IWUSR, 0,
                     BATADV_TQ_MAX_VALUE, NULL);
-BATADV_ATTR_SIF_UINT(gw_sel_class, gw.sel_class, S_IRUGO | S_IWUSR, 1,
-                    BATADV_TQ_MAX_VALUE, batadv_post_gw_reselect);
+static BATADV_ATTR(gw_sel_class, S_IRUGO | S_IWUSR, batadv_show_gw_sel_class,
+                  batadv_store_gw_sel_class);
 static BATADV_ATTR(gw_bandwidth, S_IRUGO | S_IWUSR, batadv_show_gw_bwidth,
                   batadv_store_gw_bwidth);
 #ifdef CONFIG_BATMAN_ADV_MCAST
@@ -712,6 +771,8 @@ rem_attr:
        for (bat_attr = batadv_mesh_attrs; *bat_attr; ++bat_attr)
                sysfs_remove_file(bat_priv->mesh_obj, &((*bat_attr)->attr));
 
+       kobject_uevent(bat_priv->mesh_obj, KOBJ_REMOVE);
+       kobject_del(bat_priv->mesh_obj);
        kobject_put(bat_priv->mesh_obj);
        bat_priv->mesh_obj = NULL;
 out:
@@ -726,6 +787,8 @@ void batadv_sysfs_del_meshif(struct net_device *dev)
        for (bat_attr = batadv_mesh_attrs; *bat_attr; ++bat_attr)
                sysfs_remove_file(bat_priv->mesh_obj, &((*bat_attr)->attr));
 
+       kobject_uevent(bat_priv->mesh_obj, KOBJ_REMOVE);
+       kobject_del(bat_priv->mesh_obj);
        kobject_put(bat_priv->mesh_obj);
        bat_priv->mesh_obj = NULL;
 }
@@ -781,6 +844,10 @@ rem_attr:
        for (bat_attr = batadv_vlan_attrs; *bat_attr; ++bat_attr)
                sysfs_remove_file(vlan->kobj, &((*bat_attr)->attr));
 
+       if (vlan->kobj != bat_priv->mesh_obj) {
+               kobject_uevent(vlan->kobj, KOBJ_REMOVE);
+               kobject_del(vlan->kobj);
+       }
        kobject_put(vlan->kobj);
        vlan->kobj = NULL;
 out:
@@ -800,6 +867,10 @@ void batadv_sysfs_del_vlan(struct batadv_priv *bat_priv,
        for (bat_attr = batadv_vlan_attrs; *bat_attr; ++bat_attr)
                sysfs_remove_file(vlan->kobj, &((*bat_attr)->attr));
 
+       if (vlan->kobj != bat_priv->mesh_obj) {
+               kobject_uevent(vlan->kobj, KOBJ_REMOVE);
+               kobject_del(vlan->kobj);
+       }
        kobject_put(vlan->kobj);
        vlan->kobj = NULL;
 }
@@ -828,31 +899,31 @@ static ssize_t batadv_show_mesh_iface(struct kobject *kobj,
        return length;
 }
 
-static ssize_t batadv_store_mesh_iface(struct kobject *kobj,
-                                      struct attribute *attr, char *buff,
-                                      size_t count)
+/**
+ * batadv_store_mesh_iface_finish - store new hardif mesh_iface state
+ * @net_dev: netdevice to add/remove to/from batman-adv soft-interface
+ * @ifname: name of soft-interface to modify
+ *
+ * Changes the parts of the hard+soft interface which can not be modified under
+ * sysfs lock (to prevent deadlock situations).
+ *
+ * Return: 0 on success, 0 < on failure
+ */
+static int batadv_store_mesh_iface_finish(struct net_device *net_dev,
+                                         char ifname[IFNAMSIZ])
 {
-       struct net_device *net_dev = batadv_kobj_to_netdev(kobj);
        struct net *net = dev_net(net_dev);
        struct batadv_hard_iface *hard_iface;
-       int status_tmp = -1;
-       int ret = count;
+       int status_tmp;
+       int ret = 0;
+
+       ASSERT_RTNL();
 
        hard_iface = batadv_hardif_get_by_netdev(net_dev);
        if (!hard_iface)
-               return count;
-
-       if (buff[count - 1] == '\n')
-               buff[count - 1] = '\0';
-
-       if (strlen(buff) >= IFNAMSIZ) {
-               pr_err("Invalid parameter for 'mesh_iface' setting received: interface name too long '%s'\n",
-                      buff);
-               batadv_hardif_put(hard_iface);
-               return -EINVAL;
-       }
+               return 0;
 
-       if (strncmp(buff, "none", 4) == 0)
+       if (strncmp(ifname, "none", 4) == 0)
                status_tmp = BATADV_IF_NOT_IN_USE;
        else
                status_tmp = BATADV_IF_I_WANT_YOU;
@@ -861,15 +932,13 @@ static ssize_t batadv_store_mesh_iface(struct kobject *kobj,
                goto out;
 
        if ((hard_iface->soft_iface) &&
-           (strncmp(hard_iface->soft_iface->name, buff, IFNAMSIZ) == 0))
+           (strncmp(hard_iface->soft_iface->name, ifname, IFNAMSIZ) == 0))
                goto out;
 
-       rtnl_lock();
-
        if (status_tmp == BATADV_IF_NOT_IN_USE) {
                batadv_hardif_disable_interface(hard_iface,
                                                BATADV_IF_CLEANUP_AUTO);
-               goto unlock;
+               goto out;
        }
 
        /* if the interface already is in use */
@@ -877,15 +946,71 @@ static ssize_t batadv_store_mesh_iface(struct kobject *kobj,
                batadv_hardif_disable_interface(hard_iface,
                                                BATADV_IF_CLEANUP_AUTO);
 
-       ret = batadv_hardif_enable_interface(hard_iface, net, buff);
-
-unlock:
-       rtnl_unlock();
+       ret = batadv_hardif_enable_interface(hard_iface, net, ifname);
 out:
        batadv_hardif_put(hard_iface);
        return ret;
 }
 
+/**
+ * batadv_store_mesh_iface_work - store new hardif mesh_iface state
+ * @work: work queue item
+ *
+ * Changes the parts of the hard+soft interface which can not be modified under
+ * sysfs lock (to prevent deadlock situations).
+ */
+static void batadv_store_mesh_iface_work(struct work_struct *work)
+{
+       struct batadv_store_mesh_work *store_work;
+       int ret;
+
+       store_work = container_of(work, struct batadv_store_mesh_work, work);
+
+       rtnl_lock();
+       ret = batadv_store_mesh_iface_finish(store_work->net_dev,
+                                            store_work->soft_iface_name);
+       rtnl_unlock();
+
+       if (ret < 0)
+               pr_err("Failed to store new mesh_iface state %s for %s: %d\n",
+                      store_work->soft_iface_name, store_work->net_dev->name,
+                      ret);
+
+       dev_put(store_work->net_dev);
+       kfree(store_work);
+}
+
+static ssize_t batadv_store_mesh_iface(struct kobject *kobj,
+                                      struct attribute *attr, char *buff,
+                                      size_t count)
+{
+       struct net_device *net_dev = batadv_kobj_to_netdev(kobj);
+       struct batadv_store_mesh_work *store_work;
+
+       if (buff[count - 1] == '\n')
+               buff[count - 1] = '\0';
+
+       if (strlen(buff) >= IFNAMSIZ) {
+               pr_err("Invalid parameter for 'mesh_iface' setting received: interface name too long '%s'\n",
+                      buff);
+               return -EINVAL;
+       }
+
+       store_work = kmalloc(sizeof(*store_work), GFP_KERNEL);
+       if (!store_work)
+               return -ENOMEM;
+
+       dev_hold(net_dev);
+       INIT_WORK(&store_work->work, batadv_store_mesh_iface_work);
+       store_work->net_dev = net_dev;
+       strlcpy(store_work->soft_iface_name, buff,
+               sizeof(store_work->soft_iface_name));
+
+       queue_work(batadv_event_workqueue, &store_work->work);
+
+       return count;
+}
+
 static ssize_t batadv_show_iface_status(struct kobject *kobj,
                                        struct attribute *attr, char *buff)
 {
@@ -1048,6 +1173,8 @@ out:
 
 void batadv_sysfs_del_hardif(struct kobject **hardif_obj)
 {
+       kobject_uevent(*hardif_obj, KOBJ_REMOVE);
+       kobject_del(*hardif_obj);
        kobject_put(*hardif_obj);
        *hardif_obj = NULL;
 }
This page took 0.033096 seconds and 5 git commands to generate.