msm: rmnet_sdio: Handle Network TX Queue Race Condition

The netif TX queue is started and stopped based upon the watermark state
maintained by SDIO DMUX.  The queue is checked after TX-enqueue
operations to disable the queue if the high watermark is hit and after
TX-dequeue operations to enable the queue if the low watermark is hit.

Without locking, this opens a race condition where the TX-dequeue check
and the call to stop the queue can happen after the write-done
completion has occurred which would restart the queue.  This results in
the queue being stopped indefinitely which results in a permanent data
stall.

CRs-Fixed: 354334
Change-Id: Id4d96c9216e921bf3179175bd1b1cfd39403a3d2
Signed-off-by: Eric Holmberg <eholmber@codeaurora.org>
diff --git a/drivers/net/msm_rmnet_sdio.c b/drivers/net/msm_rmnet_sdio.c
index 883c649..acdffd1 100644
--- a/drivers/net/msm_rmnet_sdio.c
+++ b/drivers/net/msm_rmnet_sdio.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 and
@@ -78,6 +78,7 @@
 #endif
 	struct sk_buff *skb;
 	spinlock_t lock;
+	spinlock_t tx_queue_lock;
 	struct tasklet_struct tsklt;
 	u32 operation_mode; /* IOCTL specified mode (protocol, QoS header) */
 	uint8_t device_up;
@@ -361,6 +362,7 @@
 static void sdio_write_done(void *dev, struct sk_buff *skb)
 {
 	struct rmnet_private *p = netdev_priv(dev);
+	unsigned long flags;
 
 	if (skb)
 		dev_kfree_skb_any(skb);
@@ -368,12 +370,14 @@
 	if (!p->in_reset) {
 		DBG1("%s: write complete skb=%p\n",	__func__, skb);
 
+		spin_lock_irqsave(&p->tx_queue_lock, flags);
 		if (netif_queue_stopped(dev) &&
 				msm_sdio_dmux_is_ch_low(p->ch_id)) {
 			DBG0("%s: Low WM hit, waking queue=%p\n",
 					__func__, skb);
 			netif_wake_queue(dev);
 		}
+		spin_unlock_irqrestore(&p->tx_queue_lock, flags);
 	} else {
 		DBG1("%s: write in reset skb=%p\n",	__func__, skb);
 	}
@@ -454,6 +458,7 @@
 static int rmnet_xmit(struct sk_buff *skb, struct net_device *dev)
 {
 	struct rmnet_private *p = netdev_priv(dev);
+	unsigned long flags;
 
 	if (netif_queue_stopped(dev)) {
 		pr_err("[%s]fatal: rmnet_xmit called when "
@@ -463,10 +468,12 @@
 
 	_rmnet_xmit(skb, dev);
 
+	spin_lock_irqsave(&p->tx_queue_lock, flags);
 	if (msm_sdio_dmux_is_ch_full(p->ch_id)) {
 		netif_stop_queue(dev);
 		DBG0("%s: High WM hit, stopping queue=%p\n",	__func__, skb);
 	}
+	spin_unlock_irqrestore(&p->tx_queue_lock, flags);
 
 	return 0;
 }
@@ -667,6 +674,7 @@
 		p->operation_mode = RMNET_MODE_LLP_ETH;
 		p->ch_id = n;
 		spin_lock_init(&p->lock);
+		spin_lock_init(&p->tx_queue_lock);
 #ifdef CONFIG_MSM_RMNET_DEBUG
 		p->timeout_us = timeout_us;
 		p->wakeups_xmit = p->wakeups_rcv = 0;