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;