Merge tag 'upstream-3.19-rc1' of git://git.infradead.org/linux-ubifs
[firefly-linux-kernel-4.4.55.git] / net / ieee802154 / core.c
index dc294a415d05f913f4f236bd735bcfff34f3b5fc..18bc7e7385074c4b62e5e7c87c1d2bba3981e7a4 100644 (file)
 #include <linux/device.h>
 
 #include <net/cfg802154.h>
+#include <net/rtnetlink.h>
 
 #include "ieee802154.h"
+#include "nl802154.h"
 #include "sysfs.h"
+#include "core.h"
 
-static DEFINE_MUTEX(wpan_phy_mutex);
-static int wpan_phy_idx;
+/* RCU-protected (and RTNL for writers) */
+LIST_HEAD(cfg802154_rdev_list);
+int cfg802154_rdev_list_generation;
 
 static int wpan_phy_match(struct device *dev, const void *data)
 {
@@ -71,51 +75,116 @@ int wpan_phy_for_each(int (*fn)(struct wpan_phy *phy, void *data),
 }
 EXPORT_SYMBOL(wpan_phy_for_each);
 
-static int wpan_phy_idx_valid(int idx)
+struct cfg802154_registered_device *
+cfg802154_rdev_by_wpan_phy_idx(int wpan_phy_idx)
 {
-       return idx >= 0;
+       struct cfg802154_registered_device *result = NULL, *rdev;
+
+       ASSERT_RTNL();
+
+       list_for_each_entry(rdev, &cfg802154_rdev_list, list) {
+               if (rdev->wpan_phy_idx == wpan_phy_idx) {
+                       result = rdev;
+                       break;
+               }
+       }
+
+       return result;
 }
 
-struct wpan_phy *wpan_phy_alloc(size_t priv_size)
+struct wpan_phy *
+wpan_phy_new(const struct cfg802154_ops *ops, size_t priv_size)
 {
-       struct wpan_phy *phy = kzalloc(sizeof(*phy) + priv_size,
-                       GFP_KERNEL);
-
-       if (!phy)
-               goto out;
-       mutex_lock(&wpan_phy_mutex);
-       phy->idx = wpan_phy_idx++;
-       if (unlikely(!wpan_phy_idx_valid(phy->idx))) {
-               wpan_phy_idx--;
-               mutex_unlock(&wpan_phy_mutex);
-               kfree(phy);
-               goto out;
+       static atomic_t wpan_phy_counter = ATOMIC_INIT(0);
+       struct cfg802154_registered_device *rdev;
+       size_t alloc_size;
+
+       alloc_size = sizeof(*rdev) + priv_size;
+       rdev = kzalloc(alloc_size, GFP_KERNEL);
+       if (!rdev)
+               return NULL;
+
+       rdev->ops = ops;
+
+       rdev->wpan_phy_idx = atomic_inc_return(&wpan_phy_counter);
+
+       if (unlikely(rdev->wpan_phy_idx < 0)) {
+               /* ugh, wrapped! */
+               atomic_dec(&wpan_phy_counter);
+               kfree(rdev);
+               return NULL;
        }
-       mutex_unlock(&wpan_phy_mutex);
 
-       mutex_init(&phy->pib_lock);
+       /* atomic_inc_return makes it start at 1, make it start at 0 */
+       rdev->wpan_phy_idx--;
+
+       mutex_init(&rdev->wpan_phy.pib_lock);
 
-       device_initialize(&phy->dev);
-       dev_set_name(&phy->dev, "wpan-phy%d", phy->idx);
+       INIT_LIST_HEAD(&rdev->wpan_dev_list);
+       device_initialize(&rdev->wpan_phy.dev);
+       dev_set_name(&rdev->wpan_phy.dev, "wpan-phy%d", rdev->wpan_phy_idx);
 
-       phy->dev.class = &wpan_phy_class;
+       rdev->wpan_phy.dev.class = &wpan_phy_class;
+       rdev->wpan_phy.dev.platform_data = rdev;
 
-       return phy;
+       init_waitqueue_head(&rdev->dev_wait);
 
-out:
-       return NULL;
+       return &rdev->wpan_phy;
 }
-EXPORT_SYMBOL(wpan_phy_alloc);
+EXPORT_SYMBOL(wpan_phy_new);
 
 int wpan_phy_register(struct wpan_phy *phy)
 {
-       return device_add(&phy->dev);
+       struct cfg802154_registered_device *rdev = wpan_phy_to_rdev(phy);
+       int ret;
+
+       rtnl_lock();
+       ret = device_add(&phy->dev);
+       if (ret) {
+               rtnl_unlock();
+               return ret;
+       }
+
+       list_add_rcu(&rdev->list, &cfg802154_rdev_list);
+       cfg802154_rdev_list_generation++;
+
+       /* TODO phy registered lock */
+       rtnl_unlock();
+
+       /* TODO nl802154 phy notify */
+
+       return 0;
 }
 EXPORT_SYMBOL(wpan_phy_register);
 
 void wpan_phy_unregister(struct wpan_phy *phy)
 {
+       struct cfg802154_registered_device *rdev = wpan_phy_to_rdev(phy);
+
+       wait_event(rdev->dev_wait, ({
+               int __count;
+               rtnl_lock();
+               __count = rdev->opencount;
+               rtnl_unlock();
+               __count == 0; }));
+
+       rtnl_lock();
+       /* TODO nl802154 phy notify */
+       /* TODO phy registered lock */
+
+       WARN_ON(!list_empty(&rdev->wpan_dev_list));
+
+       /* First remove the hardware from everywhere, this makes
+        * it impossible to find from userspace.
+        */
+       list_del_rcu(&rdev->list);
+       synchronize_rcu();
+
+       cfg802154_rdev_list_generation++;
+
        device_del(&phy->dev);
+
+       rtnl_unlock();
 }
 EXPORT_SYMBOL(wpan_phy_unregister);
 
@@ -125,6 +194,84 @@ void wpan_phy_free(struct wpan_phy *phy)
 }
 EXPORT_SYMBOL(wpan_phy_free);
 
+void cfg802154_dev_free(struct cfg802154_registered_device *rdev)
+{
+       kfree(rdev);
+}
+
+static void
+cfg802154_update_iface_num(struct cfg802154_registered_device *rdev,
+                          int iftype, int num)
+{
+       ASSERT_RTNL();
+
+       rdev->num_running_ifaces += num;
+}
+
+static int cfg802154_netdev_notifier_call(struct notifier_block *nb,
+                                         unsigned long state, void *ptr)
+{
+       struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+       struct wpan_dev *wpan_dev = dev->ieee802154_ptr;
+       struct cfg802154_registered_device *rdev;
+
+       if (!wpan_dev)
+               return NOTIFY_DONE;
+
+       rdev = wpan_phy_to_rdev(wpan_dev->wpan_phy);
+
+       /* TODO WARN_ON unspec type */
+
+       switch (state) {
+               /* TODO NETDEV_DEVTYPE */
+       case NETDEV_REGISTER:
+               wpan_dev->identifier = ++rdev->wpan_dev_id;
+               list_add_rcu(&wpan_dev->list, &rdev->wpan_dev_list);
+               rdev->devlist_generation++;
+
+               wpan_dev->netdev = dev;
+               break;
+       case NETDEV_DOWN:
+               cfg802154_update_iface_num(rdev, wpan_dev->iftype, -1);
+
+               rdev->opencount--;
+               wake_up(&rdev->dev_wait);
+               break;
+       case NETDEV_UP:
+               cfg802154_update_iface_num(rdev, wpan_dev->iftype, 1);
+
+               rdev->opencount++;
+               break;
+       case NETDEV_UNREGISTER:
+               /* It is possible to get NETDEV_UNREGISTER
+                * multiple times. To detect that, check
+                * that the interface is still on the list
+                * of registered interfaces, and only then
+                * remove and clean it up.
+                */
+               if (!list_empty(&wpan_dev->list)) {
+                       list_del_rcu(&wpan_dev->list);
+                       rdev->devlist_generation++;
+               }
+               /* synchronize (so that we won't find this netdev
+                * from other code any more) and then clear the list
+                * head so that the above code can safely check for
+                * !list_empty() to avoid double-cleanup.
+                */
+               synchronize_rcu();
+               INIT_LIST_HEAD(&wpan_dev->list);
+               break;
+       default:
+               return NOTIFY_DONE;
+       }
+
+       return NOTIFY_OK;
+}
+
+static struct notifier_block cfg802154_netdev_notifier = {
+       .notifier_call = cfg802154_netdev_notifier_call,
+};
+
 static int __init wpan_phy_class_init(void)
 {
        int rc;
@@ -133,11 +280,25 @@ static int __init wpan_phy_class_init(void)
        if (rc)
                goto err;
 
-       rc = ieee802154_nl_init();
+       rc = register_netdevice_notifier(&cfg802154_netdev_notifier);
        if (rc)
                goto err_nl;
 
+       rc = ieee802154_nl_init();
+       if (rc)
+               goto err_notifier;
+
+       rc = nl802154_init();
+       if (rc)
+               goto err_ieee802154_nl;
+
        return 0;
+
+err_ieee802154_nl:
+       ieee802154_nl_exit();
+
+err_notifier:
+       unregister_netdevice_notifier(&cfg802154_netdev_notifier);
 err_nl:
        wpan_phy_sysfs_exit();
 err:
@@ -147,7 +308,9 @@ subsys_initcall(wpan_phy_class_init);
 
 static void __exit wpan_phy_class_exit(void)
 {
+       nl802154_exit();
        ieee802154_nl_exit();
+       unregister_netdevice_notifier(&cfg802154_netdev_notifier);
        wpan_phy_sysfs_exit();
 }
 module_exit(wpan_phy_class_exit);