Commit 7e1112d3 authored by Lennert Buytenhek's avatar Lennert Buytenhek Committed by John W. Linville

mwl8k: allow more time for transmit rings to drain

Before issuing any firmware commands, we wait for the transmit rings
to drain, to prevent control versus data path synchronization issues.
In some cases, this can end up taking longer than the current hardcoded
limit of 5 seconds, for example if the transmit rings are filled with
packets for a host that has dropped off the air and we end up
retransmitting every pending packet at the lowest rate a couple of
times.

This patch changes mwl8k_tx_wait_empty() to only bail out on timeout
expiry if there was no change in the number of packets pending in the
transmit rings during the waiting period.  If at least one transmit
ring entry was reclaimed while we were waiting, we are apparently still
making progress, and we'll allow waiting for another timeout period.
Signed-off-by: default avatarLennert Buytenhek <buytenh@marvell.com>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent 0c9cc640
...@@ -1237,99 +1237,106 @@ static inline void mwl8k_tx_start(struct mwl8k_priv *priv) ...@@ -1237,99 +1237,106 @@ static inline void mwl8k_tx_start(struct mwl8k_priv *priv)
ioread32(priv->regs + MWL8K_HIU_INT_CODE); ioread32(priv->regs + MWL8K_HIU_INT_CODE);
} }
struct mwl8k_txq_info { static void mwl8k_dump_tx_rings(struct ieee80211_hw *hw)
u32 fw_owned;
u32 drv_owned;
u32 unused;
u32 len;
u32 head;
u32 tail;
};
static int mwl8k_scan_tx_ring(struct mwl8k_priv *priv,
struct mwl8k_txq_info *txinfo)
{ {
int count, desc, status; struct mwl8k_priv *priv = hw->priv;
struct mwl8k_tx_queue *txq; int i;
struct mwl8k_tx_desc *tx_desc;
int ndescs = 0;
memset(txinfo, 0, MWL8K_TX_QUEUES * sizeof(struct mwl8k_txq_info)); for (i = 0; i < MWL8K_TX_QUEUES; i++) {
struct mwl8k_tx_queue *txq = priv->txq + i;
int fw_owned = 0;
int drv_owned = 0;
int unused = 0;
int desc;
for (count = 0; count < MWL8K_TX_QUEUES; count++) {
txq = priv->txq + count;
txinfo[count].len = txq->stats.len;
txinfo[count].head = txq->head;
txinfo[count].tail = txq->tail;
for (desc = 0; desc < MWL8K_TX_DESCS; desc++) { for (desc = 0; desc < MWL8K_TX_DESCS; desc++) {
tx_desc = txq->txd + desc; struct mwl8k_tx_desc *tx_desc = txq->txd + desc;
status = le32_to_cpu(tx_desc->status); u32 status;
status = le32_to_cpu(tx_desc->status);
if (status & MWL8K_TXD_STATUS_FW_OWNED) if (status & MWL8K_TXD_STATUS_FW_OWNED)
txinfo[count].fw_owned++; fw_owned++;
else else
txinfo[count].drv_owned++; drv_owned++;
if (tx_desc->pkt_len == 0) if (tx_desc->pkt_len == 0)
txinfo[count].unused++; unused++;
}
} }
return ndescs; printk(KERN_ERR "%s: txq[%d] len=%d head=%d tail=%d "
"fw_owned=%d drv_owned=%d unused=%d\n",
wiphy_name(hw->wiphy), i,
txq->stats.len, txq->head, txq->tail,
fw_owned, drv_owned, unused);
}
} }
/* /*
* Must be called with priv->fw_mutex held and tx queues stopped. * Must be called with priv->fw_mutex held and tx queues stopped.
*/ */
#define MWL8K_TX_WAIT_TIMEOUT_MS 1000
static int mwl8k_tx_wait_empty(struct ieee80211_hw *hw) static int mwl8k_tx_wait_empty(struct ieee80211_hw *hw)
{ {
struct mwl8k_priv *priv = hw->priv; struct mwl8k_priv *priv = hw->priv;
DECLARE_COMPLETION_ONSTACK(tx_wait); DECLARE_COMPLETION_ONSTACK(tx_wait);
u32 count; int retry;
unsigned long timeout; int rc;
might_sleep(); might_sleep();
/*
* The TX queues are stopped at this point, so this test
* doesn't need to take ->tx_lock.
*/
if (!priv->pending_tx_pkts)
return 0;
retry = 0;
rc = 0;
spin_lock_bh(&priv->tx_lock); spin_lock_bh(&priv->tx_lock);
count = priv->pending_tx_pkts;
if (count)
priv->tx_wait = &tx_wait; priv->tx_wait = &tx_wait;
spin_unlock_bh(&priv->tx_lock); while (!rc) {
int oldcount;
unsigned long timeout;
if (count) { oldcount = priv->pending_tx_pkts;
struct mwl8k_txq_info txinfo[MWL8K_TX_QUEUES];
int index;
int newcount;
spin_unlock_bh(&priv->tx_lock);
timeout = wait_for_completion_timeout(&tx_wait, timeout = wait_for_completion_timeout(&tx_wait,
msecs_to_jiffies(5000)); msecs_to_jiffies(MWL8K_TX_WAIT_TIMEOUT_MS));
if (timeout)
return 0;
spin_lock_bh(&priv->tx_lock); spin_lock_bh(&priv->tx_lock);
priv->tx_wait = NULL;
newcount = priv->pending_tx_pkts;
mwl8k_scan_tx_ring(priv, txinfo);
spin_unlock_bh(&priv->tx_lock);
printk(KERN_ERR "%s(%u) TIMEDOUT:5000ms Pend:%u-->%u\n", if (timeout) {
__func__, __LINE__, count, newcount); WARN_ON(priv->pending_tx_pkts);
if (retry) {
printk(KERN_NOTICE "%s: tx rings drained\n",
wiphy_name(hw->wiphy));
}
break;
}
if (priv->pending_tx_pkts < oldcount) {
printk(KERN_NOTICE "%s: timeout waiting for tx "
"rings to drain (%d -> %d pkts), retrying\n",
wiphy_name(hw->wiphy), oldcount,
priv->pending_tx_pkts);
retry = 1;
continue;
}
priv->tx_wait = NULL;
for (index = 0; index < MWL8K_TX_QUEUES; index++) printk(KERN_ERR "%s: tx rings stuck for %d ms\n",
printk(KERN_ERR "TXQ:%u L:%u H:%u T:%u FW:%u " wiphy_name(hw->wiphy), MWL8K_TX_WAIT_TIMEOUT_MS);
"DRV:%u U:%u\n", mwl8k_dump_tx_rings(hw);
index,
txinfo[index].len,
txinfo[index].head,
txinfo[index].tail,
txinfo[index].fw_owned,
txinfo[index].drv_owned,
txinfo[index].unused);
return -ETIMEDOUT; rc = -ETIMEDOUT;
} }
spin_unlock_bh(&priv->tx_lock);
return 0; return rc;
} }
#define MWL8K_TXD_SUCCESS(status) \ #define MWL8K_TXD_SUCCESS(status) \
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment