sunvnet: Schedule maybe_tx_wakeup() as a tasklet from ldc_rx path
authorSowmini Varadhan <sowmini.varadhan@oracle.com>
Wed, 13 Aug 2014 14:29:41 +0000 (10:29 -0400)
committerDavid S. Miller <davem@davemloft.net>
Thu, 14 Aug 2014 03:04:46 +0000 (20:04 -0700)
At the tail of vnet_event(), if we hit the maybe_tx_wakeup()
condition, we try to take the netif_tx_lock() in the
recv-interrupt-context and can deadlock with dev_watchdog().
vnet_event() should schedule maybe_tx_wakeup() as a tasklet
to avoid this deadlock

Signed-off-by: Sowmini Varadhan <sowmini.varadhan@oracle.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/sun/sunvnet.c
drivers/net/ethernet/sun/sunvnet.h

index a5871791e45243e389a9a84529c72b47ad0fec50..23c89ab5a6ada1ade365939dcd7bb28da44ca736 100644 (file)
@@ -488,8 +488,9 @@ static int handle_mcast(struct vnet_port *port, void *msgbuf)
        return 0;
 }
 
-static void maybe_tx_wakeup(struct vnet *vp)
+static void maybe_tx_wakeup(unsigned long param)
 {
+       struct vnet *vp = (struct vnet *)param;
        struct net_device *dev = vp->dev;
 
        netif_tx_lock(dev);
@@ -586,8 +587,13 @@ static void vnet_event(void *arg, int event)
                        break;
        }
        spin_unlock(&vio->lock);
+       /* Kick off a tasklet to wake the queue.  We cannot call
+        * maybe_tx_wakeup directly here because we could deadlock on
+        * netif_tx_lock() with dev_watchdog()
+        */
        if (unlikely(tx_wakeup && err != -ECONNRESET))
-               maybe_tx_wakeup(port->vp);
+               tasklet_schedule(&port->vp->vnet_tx_wakeup);
+
        local_irq_restore(flags);
 }
 
@@ -1070,6 +1076,7 @@ static struct vnet *vnet_new(const u64 *local_mac)
        vp = netdev_priv(dev);
 
        spin_lock_init(&vp->lock);
+       tasklet_init(&vp->vnet_tx_wakeup, maybe_tx_wakeup, (unsigned long)vp);
        vp->dev = dev;
 
        INIT_LIST_HEAD(&vp->port_list);
@@ -1129,6 +1136,7 @@ static void vnet_cleanup(void)
                vp = list_first_entry(&vnet_list, struct vnet, list);
                list_del(&vp->list);
                dev = vp->dev;
+               tasklet_kill(&vp->vnet_tx_wakeup);
                /* vio_unregister_driver() should have cleaned up port_list */
                BUG_ON(!list_empty(&vp->port_list));
                unregister_netdev(dev);
index d347a5bf24b00d1f69e50dc78106086ae3e99eac..de5c2c64996f34c5eb76e5376a71d93b72ebf490 100644 (file)
@@ -1,6 +1,8 @@
 #ifndef _SUNVNET_H
 #define _SUNVNET_H
 
+#include <linux/interrupt.h>
+
 #define DESC_NCOOKIES(entry_size)      \
        ((entry_size) - sizeof(struct vio_net_desc))
 
@@ -78,6 +80,8 @@ struct vnet {
 
        struct list_head        list;
        u64                     local_mac;
+
+       struct tasklet_struct   vnet_tx_wakeup;
 };
 
 #endif /* _SUNVNET_H */