Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/trivial
[pandora-kernel.git] / net / caif / caif_dev.c
index a42a408..682c0fe 100644 (file)
 #define pr_fmt(fmt) KBUILD_MODNAME ":%s(): " fmt, __func__
 
 #include <linux/version.h>
-#include <linux/module.h>
 #include <linux/kernel.h>
 #include <linux/if_arp.h>
 #include <linux/net.h>
 #include <linux/netdevice.h>
-#include <linux/skbuff.h>
-#include <linux/sched.h>
-#include <linux/wait.h>
+#include <linux/mutex.h>
 #include <net/netns/generic.h>
 #include <net/net_namespace.h>
 #include <net/pkt_sched.h>
 #include <net/caif/caif_device.h>
-#include <net/caif/caif_dev.h>
 #include <net/caif/caif_layer.h>
 #include <net/caif/cfpkt.h>
 #include <net/caif/cfcnfg.h>
 
 MODULE_LICENSE("GPL");
-#define TIMEOUT (HZ*5)
 
 /* Used for local tracking of the CAIF net devices */
 struct caif_device_entry {
        struct cflayer layer;
        struct list_head list;
-       atomic_t in_use;
-       atomic_t state;
-       u16 phyid;
        struct net_device *netdev;
-       wait_queue_head_t event;
+       int __percpu *pcpu_refcnt;
 };
 
 struct caif_device_entry_list {
        struct list_head list;
        /* Protects simulanous deletes in list */
-       spinlock_t lock;
+       struct mutex lock;
 };
 
 struct caif_net {
+       struct cfcnfg *cfg;
        struct caif_device_entry_list caifdevs;
 };
 
 static int caif_net_id;
-static struct cfcnfg *cfg;
+
+struct cfcnfg *get_cfcnfg(struct net *net)
+{
+       struct caif_net *caifn;
+       BUG_ON(!net);
+       caifn = net_generic(net, caif_net_id);
+       BUG_ON(!caifn);
+       return caifn->cfg;
+}
+EXPORT_SYMBOL(get_cfcnfg);
 
 static struct caif_device_entry_list *caif_device_list(struct net *net)
 {
@@ -65,19 +67,39 @@ static struct caif_device_entry_list *caif_device_list(struct net *net)
        return &caifn->caifdevs;
 }
 
+static void caifd_put(struct caif_device_entry *e)
+{
+       irqsafe_cpu_dec(*e->pcpu_refcnt);
+}
+
+static void caifd_hold(struct caif_device_entry *e)
+{
+       irqsafe_cpu_inc(*e->pcpu_refcnt);
+}
+
+static int caifd_refcnt_read(struct caif_device_entry *e)
+{
+       int i, refcnt = 0;
+       for_each_possible_cpu(i)
+               refcnt += *per_cpu_ptr(e->pcpu_refcnt, i);
+       return refcnt;
+}
+
 /* Allocate new CAIF device. */
 static struct caif_device_entry *caif_device_alloc(struct net_device *dev)
 {
        struct caif_device_entry_list *caifdevs;
        struct caif_device_entry *caifd;
+
        caifdevs = caif_device_list(dev_net(dev));
        BUG_ON(!caifdevs);
+
        caifd = kzalloc(sizeof(*caifd), GFP_ATOMIC);
        if (!caifd)
                return NULL;
+       caifd->pcpu_refcnt = alloc_percpu(int);
        caifd->netdev = dev;
-       list_add(&caifd->list, &caifdevs->list);
-       init_waitqueue_head(&caifd->event);
+       dev_hold(dev);
        return caifd;
 }
 
@@ -87,98 +109,65 @@ static struct caif_device_entry *caif_get(struct net_device *dev)
            caif_device_list(dev_net(dev));
        struct caif_device_entry *caifd;
        BUG_ON(!caifdevs);
-       list_for_each_entry(caifd, &caifdevs->list, list) {
+       list_for_each_entry_rcu(caifd, &caifdevs->list, list) {
                if (caifd->netdev == dev)
                        return caifd;
        }
        return NULL;
 }
 
-static void caif_device_destroy(struct net_device *dev)
-{
-       struct caif_device_entry_list *caifdevs =
-           caif_device_list(dev_net(dev));
-       struct caif_device_entry *caifd;
-       ASSERT_RTNL();
-       if (dev->type != ARPHRD_CAIF)
-               return;
-
-       spin_lock_bh(&caifdevs->lock);
-       caifd = caif_get(dev);
-       if (caifd == NULL) {
-               spin_unlock_bh(&caifdevs->lock);
-               return;
-       }
-
-       list_del(&caifd->list);
-       spin_unlock_bh(&caifdevs->lock);
-
-       kfree(caifd);
-}
-
 static int transmit(struct cflayer *layer, struct cfpkt *pkt)
 {
+       int err;
        struct caif_device_entry *caifd =
            container_of(layer, struct caif_device_entry, layer);
-       struct sk_buff *skb, *skb2;
-       int ret = -EINVAL;
+       struct sk_buff *skb;
+
        skb = cfpkt_tonative(pkt);
        skb->dev = caifd->netdev;
-       /*
-        * Don't allow SKB to be destroyed upon error, but signal resend
-        * notification to clients. We can't rely on the return value as
-        * congestion (NET_XMIT_CN) sometimes drops the packet, sometimes don't.
-        */
-       if (netif_queue_stopped(caifd->netdev))
-               return -EAGAIN;
-       skb2 = skb_get(skb);
-
-       ret = dev_queue_xmit(skb2);
-
-       if (!ret)
-               kfree_skb(skb);
-       else
-               return -EAGAIN;
 
-       return 0;
-}
+       err = dev_queue_xmit(skb);
+       if (err > 0)
+               err = -EIO;
 
-static int modemcmd(struct cflayer *layr, enum caif_modemcmd ctrl)
-{
-       struct caif_device_entry *caifd;
-       struct caif_dev_common *caifdev;
-       caifd = container_of(layr, struct caif_device_entry, layer);
-       caifdev = netdev_priv(caifd->netdev);
-       if (ctrl == _CAIF_MODEMCMD_PHYIF_USEFULL) {
-               atomic_set(&caifd->in_use, 1);
-               wake_up_interruptible(&caifd->event);
-
-       } else if (ctrl == _CAIF_MODEMCMD_PHYIF_USELESS) {
-               atomic_set(&caifd->in_use, 0);
-               wake_up_interruptible(&caifd->event);
-       }
-       return 0;
+       return err;
 }
 
 /*
- * Stuff received packets to associated sockets.
+ * Stuff received packets into the CAIF stack.
  * On error, returns non-zero and releases the skb.
  */
 static int receive(struct sk_buff *skb, struct net_device *dev,
                   struct packet_type *pkttype, struct net_device *orig_dev)
 {
-       struct net *net;
        struct cfpkt *pkt;
        struct caif_device_entry *caifd;
-       net = dev_net(dev);
+       int err;
+
        pkt = cfpkt_fromnative(CAIF_DIR_IN, skb);
+
+       rcu_read_lock();
        caifd = caif_get(dev);
-       if (!caifd || !caifd->layer.up || !caifd->layer.up->receive)
-               return NET_RX_DROP;
 
-       if (caifd->layer.up->receive(caifd->layer.up, pkt))
+       if (!caifd || !caifd->layer.up || !caifd->layer.up->receive ||
+                       !netif_oper_up(caifd->netdev)) {
+               rcu_read_unlock();
+               kfree_skb(skb);
                return NET_RX_DROP;
+       }
+
+       /* Hold reference to netdevice while using CAIF stack */
+       caifd_hold(caifd);
+       rcu_read_unlock();
+
+       err = caifd->layer.up->receive(caifd->layer.up, pkt);
 
+       /* For -EILSEQ the packet is not freed so so it now */
+       if (err == -EILSEQ)
+               cfpkt_destroy(pkt);
+
+       /* Release reference to stack upwards */
+       caifd_put(caifd);
        return 0;
 }
 
@@ -189,15 +178,25 @@ static struct packet_type caif_packet_type __read_mostly = {
 
 static void dev_flowctrl(struct net_device *dev, int on)
 {
-       struct caif_device_entry *caifd = caif_get(dev);
-       if (!caifd || !caifd->layer.up || !caifd->layer.up->ctrlcmd)
+       struct caif_device_entry *caifd;
+
+       rcu_read_lock();
+
+       caifd = caif_get(dev);
+       if (!caifd || !caifd->layer.up || !caifd->layer.up->ctrlcmd) {
+               rcu_read_unlock();
                return;
+       }
+
+       caifd_hold(caifd);
+       rcu_read_unlock();
 
        caifd->layer.up->ctrlcmd(caifd->layer.up,
                                 on ?
                                 _CAIF_CTRLCMD_PHYIF_FLOW_ON_IND :
                                 _CAIF_CTRLCMD_PHYIF_FLOW_OFF_IND,
                                 caifd->layer.id);
+       caifd_put(caifd);
 }
 
 /* notify Caif of device events */
@@ -208,37 +207,28 @@ static int caif_device_notify(struct notifier_block *me, unsigned long what,
        struct caif_device_entry *caifd = NULL;
        struct caif_dev_common *caifdev;
        enum cfcnfg_phy_preference pref;
-       int res = -EINVAL;
        enum cfcnfg_phy_type phy_type;
+       struct cfcnfg *cfg;
+       struct caif_device_entry_list *caifdevs =
+           caif_device_list(dev_net(dev));
 
        if (dev->type != ARPHRD_CAIF)
                return 0;
 
+       cfg = get_cfcnfg(dev_net(dev));
+       if (cfg == NULL)
+               return 0;
+
        switch (what) {
        case NETDEV_REGISTER:
-               netdev_info(dev, "register\n");
                caifd = caif_device_alloc(dev);
-               if (caifd == NULL)
-                       break;
+               if (!caifd)
+                       return 0;
+
                caifdev = netdev_priv(dev);
                caifdev->flowctrl = dev_flowctrl;
-               atomic_set(&caifd->state, what);
-               res = 0;
-               break;
 
-       case NETDEV_UP:
-               netdev_info(dev, "up\n");
-               caifd = caif_get(dev);
-               if (caifd == NULL)
-                       break;
-               caifdev = netdev_priv(dev);
-               if (atomic_read(&caifd->state) == NETDEV_UP) {
-                       netdev_info(dev, "already up\n");
-                       break;
-               }
-               atomic_set(&caifd->state, what);
                caifd->layer.transmit = transmit;
-               caifd->layer.modemcmd = modemcmd;
 
                if (caifdev->use_frag)
                        phy_type = CFPHYTYPE_FRAG;
@@ -256,62 +246,94 @@ static int caif_device_notify(struct notifier_block *me, unsigned long what,
                        pref = CFPHYPREF_HIGH_BW;
                        break;
                }
-               dev_hold(dev);
-               cfcnfg_add_phy_layer(get_caif_conf(),
+               strncpy(caifd->layer.name, dev->name,
+                       sizeof(caifd->layer.name) - 1);
+               caifd->layer.name[sizeof(caifd->layer.name) - 1] = 0;
+
+               mutex_lock(&caifdevs->lock);
+               list_add_rcu(&caifd->list, &caifdevs->list);
+
+               cfcnfg_add_phy_layer(cfg,
                                     phy_type,
                                     dev,
                                     &caifd->layer,
-                                    &caifd->phyid,
                                     pref,
                                     caifdev->use_fcs,
                                     caifdev->use_stx);
-               strncpy(caifd->layer.name, dev->name,
-                       sizeof(caifd->layer.name) - 1);
-               caifd->layer.name[sizeof(caifd->layer.name) - 1] = 0;
+               mutex_unlock(&caifdevs->lock);
                break;
 
-       case NETDEV_GOING_DOWN:
+       case NETDEV_UP:
+               rcu_read_lock();
+
                caifd = caif_get(dev);
-               if (caifd == NULL)
+               if (caifd == NULL) {
+                       rcu_read_unlock();
                        break;
-               netdev_info(dev, "going down\n");
+               }
 
-               if (atomic_read(&caifd->state) == NETDEV_GOING_DOWN ||
-                       atomic_read(&caifd->state) == NETDEV_DOWN)
-                       break;
+               cfcnfg_set_phy_state(cfg, &caifd->layer, true);
+               rcu_read_unlock();
 
-               atomic_set(&caifd->state, what);
-               if (!caifd || !caifd->layer.up || !caifd->layer.up->ctrlcmd)
-                       return -EINVAL;
-               caifd->layer.up->ctrlcmd(caifd->layer.up,
-                                        _CAIF_CTRLCMD_PHYIF_DOWN_IND,
-                                        caifd->layer.id);
-               might_sleep();
-               res = wait_event_interruptible_timeout(caifd->event,
-                                       atomic_read(&caifd->in_use) == 0,
-                                       TIMEOUT);
                break;
 
        case NETDEV_DOWN:
+               rcu_read_lock();
+
                caifd = caif_get(dev);
-               if (caifd == NULL)
-                       break;
-               netdev_info(dev, "down\n");
-               if (atomic_read(&caifd->in_use))
-                       netdev_warn(dev,
-                                   "Unregistering an active CAIF device\n");
-               cfcnfg_del_phy_layer(get_caif_conf(), &caifd->layer);
-               dev_put(dev);
-               atomic_set(&caifd->state, what);
+               if (!caifd || !caifd->layer.up || !caifd->layer.up->ctrlcmd) {
+                       rcu_read_unlock();
+                       return -EINVAL;
+               }
+
+               cfcnfg_set_phy_state(cfg, &caifd->layer, false);
+               caifd_hold(caifd);
+               rcu_read_unlock();
+
+               caifd->layer.up->ctrlcmd(caifd->layer.up,
+                                        _CAIF_CTRLCMD_PHYIF_DOWN_IND,
+                                        caifd->layer.id);
+               caifd_put(caifd);
                break;
 
        case NETDEV_UNREGISTER:
+               mutex_lock(&caifdevs->lock);
+
                caifd = caif_get(dev);
-               if (caifd == NULL)
+               if (caifd == NULL) {
+                       mutex_unlock(&caifdevs->lock);
+                       break;
+               }
+               list_del_rcu(&caifd->list);
+
+               /*
+                * NETDEV_UNREGISTER is called repeatedly until all reference
+                * counts for the net-device are released. If references to
+                * caifd is taken, simply ignore NETDEV_UNREGISTER and wait for
+                * the next call to NETDEV_UNREGISTER.
+                *
+                * If any packets are in flight down the CAIF Stack,
+                * cfcnfg_del_phy_layer will return nonzero.
+                * If no packets are in flight, the CAIF Stack associated
+                * with the net-device un-registering is freed.
+                */
+
+               if (caifd_refcnt_read(caifd) != 0 ||
+                       cfcnfg_del_phy_layer(cfg, &caifd->layer) != 0) {
+
+                       pr_info("Wait for device inuse\n");
+                       /* Enrole device if CAIF Stack is still in use */
+                       list_add_rcu(&caifd->list, &caifdevs->list);
+                       mutex_unlock(&caifdevs->lock);
                        break;
-               netdev_info(dev, "unregister\n");
-               atomic_set(&caifd->state, what);
-               caif_device_destroy(dev);
+               }
+
+               synchronize_rcu();
+               dev_put(caifd->netdev);
+               free_percpu(caifd->pcpu_refcnt);
+               kfree(caifd);
+
+               mutex_unlock(&caifdevs->lock);
                break;
        }
        return 0;
@@ -322,61 +344,60 @@ static struct notifier_block caif_device_notifier = {
        .priority = 0,
 };
 
-
-struct cfcnfg *get_caif_conf(void)
-{
-       return cfg;
-}
-EXPORT_SYMBOL(get_caif_conf);
-
-int caif_connect_client(struct caif_connect_request *conn_req,
-                       struct cflayer *client_layer, int *ifindex,
-                       int *headroom, int *tailroom)
-{
-       struct cfctrl_link_param param;
-       int ret;
-       ret = connect_req_to_link_param(get_caif_conf(), conn_req, &param);
-       if (ret)
-               return ret;
-       /* Hook up the adaptation layer. */
-       return cfcnfg_add_adaptation_layer(get_caif_conf(), &param,
-                                       client_layer, ifindex,
-                                       headroom, tailroom);
-}
-EXPORT_SYMBOL(caif_connect_client);
-
-int caif_disconnect_client(struct cflayer *adap_layer)
-{
-       return cfcnfg_disconn_adapt_layer(get_caif_conf(), adap_layer);
-}
-EXPORT_SYMBOL(caif_disconnect_client);
-
-void caif_release_client(struct cflayer *adap_layer)
-{
-       cfcnfg_release_adap_layer(adap_layer);
-}
-EXPORT_SYMBOL(caif_release_client);
-
 /* Per-namespace Caif devices handling */
 static int caif_init_net(struct net *net)
 {
        struct caif_net *caifn = net_generic(net, caif_net_id);
+       BUG_ON(!caifn);
        INIT_LIST_HEAD(&caifn->caifdevs.list);
-       spin_lock_init(&caifn->caifdevs.lock);
+       mutex_init(&caifn->caifdevs.lock);
+
+       caifn->cfg = cfcnfg_create();
+       if (!caifn->cfg) {
+               pr_warn("can't create cfcnfg\n");
+               return -ENOMEM;
+       }
+
        return 0;
 }
 
 static void caif_exit_net(struct net *net)
 {
-       struct net_device *dev;
-       int res;
+       struct caif_device_entry *caifd, *tmp;
+       struct caif_device_entry_list *caifdevs =
+           caif_device_list(net);
+       struct cfcnfg *cfg;
+
        rtnl_lock();
-       for_each_netdev(net, dev) {
-               if (dev->type != ARPHRD_CAIF)
-                       continue;
-               res = dev_close(dev);
-               caif_device_destroy(dev);
+       mutex_lock(&caifdevs->lock);
+
+       cfg = get_cfcnfg(net);
+       if (cfg == NULL) {
+               mutex_unlock(&caifdevs->lock);
+               return;
        }
+
+       list_for_each_entry_safe(caifd, tmp, &caifdevs->list, list) {
+               int i = 0;
+               list_del_rcu(&caifd->list);
+               cfcnfg_set_phy_state(cfg, &caifd->layer, false);
+
+               while (i < 10 &&
+                       (caifd_refcnt_read(caifd) != 0 ||
+                       cfcnfg_del_phy_layer(cfg, &caifd->layer) != 0)) {
+
+                       pr_info("Wait for device inuse\n");
+                       msleep(250);
+                       i++;
+               }
+               synchronize_rcu();
+               dev_put(caifd->netdev);
+               free_percpu(caifd->pcpu_refcnt);
+               kfree(caifd);
+       }
+       cfcnfg_remove(cfg);
+
+       mutex_unlock(&caifdevs->lock);
        rtnl_unlock();
 }
 
@@ -391,32 +412,23 @@ static struct pernet_operations caif_net_ops = {
 static int __init caif_device_init(void)
 {
        int result;
-       cfg = cfcnfg_create();
-       if (!cfg) {
-               pr_warn("can't create cfcnfg\n");
-               goto err_cfcnfg_create_failed;
-       }
+
        result = register_pernet_device(&caif_net_ops);
 
-       if (result) {
-               kfree(cfg);
-               cfg = NULL;
+       if (result)
                return result;
-       }
-       dev_add_pack(&caif_packet_type);
+
        register_netdevice_notifier(&caif_device_notifier);
+       dev_add_pack(&caif_packet_type);
 
        return result;
-err_cfcnfg_create_failed:
-       return -ENODEV;
 }
 
 static void __exit caif_device_exit(void)
 {
-       dev_remove_pack(&caif_packet_type);
        unregister_pernet_device(&caif_net_ops);
        unregister_netdevice_notifier(&caif_device_notifier);
-       cfcnfg_remove(cfg);
+       dev_remove_pack(&caif_packet_type);
 }
 
 module_init(caif_device_init);