|
@@ -1237,99 +1237,106 @@ static inline void mwl8k_tx_start(struct mwl8k_priv *priv)
|
|
|
ioread32(priv->regs + MWL8K_HIU_INT_CODE);
|
|
|
}
|
|
|
|
|
|
-struct mwl8k_txq_info {
|
|
|
- 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)
|
|
|
+static void mwl8k_dump_tx_rings(struct ieee80211_hw *hw)
|
|
|
{
|
|
|
- int count, desc, status;
|
|
|
- struct mwl8k_tx_queue *txq;
|
|
|
- struct mwl8k_tx_desc *tx_desc;
|
|
|
- int ndescs = 0;
|
|
|
+ struct mwl8k_priv *priv = hw->priv;
|
|
|
+ int i;
|
|
|
|
|
|
- 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++) {
|
|
|
- tx_desc = txq->txd + desc;
|
|
|
- status = le32_to_cpu(tx_desc->status);
|
|
|
+ struct mwl8k_tx_desc *tx_desc = txq->txd + desc;
|
|
|
+ u32 status;
|
|
|
|
|
|
+ status = le32_to_cpu(tx_desc->status);
|
|
|
if (status & MWL8K_TXD_STATUS_FW_OWNED)
|
|
|
- txinfo[count].fw_owned++;
|
|
|
+ fw_owned++;
|
|
|
else
|
|
|
- txinfo[count].drv_owned++;
|
|
|
+ drv_owned++;
|
|
|
|
|
|
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.
|
|
|
*/
|
|
|
+#define MWL8K_TX_WAIT_TIMEOUT_MS 1000
|
|
|
+
|
|
|
static int mwl8k_tx_wait_empty(struct ieee80211_hw *hw)
|
|
|
{
|
|
|
struct mwl8k_priv *priv = hw->priv;
|
|
|
DECLARE_COMPLETION_ONSTACK(tx_wait);
|
|
|
- u32 count;
|
|
|
- unsigned long timeout;
|
|
|
+ int retry;
|
|
|
+ int rc;
|
|
|
|
|
|
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);
|
|
|
- count = priv->pending_tx_pkts;
|
|
|
- if (count)
|
|
|
- priv->tx_wait = &tx_wait;
|
|
|
- spin_unlock_bh(&priv->tx_lock);
|
|
|
+ priv->tx_wait = &tx_wait;
|
|
|
+ while (!rc) {
|
|
|
+ int oldcount;
|
|
|
+ unsigned long timeout;
|
|
|
|
|
|
- if (count) {
|
|
|
- struct mwl8k_txq_info txinfo[MWL8K_TX_QUEUES];
|
|
|
- int index;
|
|
|
- int newcount;
|
|
|
+ oldcount = priv->pending_tx_pkts;
|
|
|
|
|
|
+ spin_unlock_bh(&priv->tx_lock);
|
|
|
timeout = wait_for_completion_timeout(&tx_wait,
|
|
|
- msecs_to_jiffies(5000));
|
|
|
- if (timeout)
|
|
|
- return 0;
|
|
|
-
|
|
|
+ msecs_to_jiffies(MWL8K_TX_WAIT_TIMEOUT_MS));
|
|
|
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",
|
|
|
- __func__, __LINE__, count, newcount);
|
|
|
+ if (timeout) {
|
|
|
+ WARN_ON(priv->pending_tx_pkts);
|
|
|
+ if (retry) {
|
|
|
+ printk(KERN_NOTICE "%s: tx rings drained\n",
|
|
|
+ wiphy_name(hw->wiphy));
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
|
|
|
- for (index = 0; index < MWL8K_TX_QUEUES; index++)
|
|
|
- printk(KERN_ERR "TXQ:%u L:%u H:%u T:%u FW:%u "
|
|
|
- "DRV:%u U:%u\n",
|
|
|
- index,
|
|
|
- txinfo[index].len,
|
|
|
- txinfo[index].head,
|
|
|
- txinfo[index].tail,
|
|
|
- txinfo[index].fw_owned,
|
|
|
- txinfo[index].drv_owned,
|
|
|
- txinfo[index].unused);
|
|
|
+ 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;
|
|
|
+
|
|
|
+ printk(KERN_ERR "%s: tx rings stuck for %d ms\n",
|
|
|
+ wiphy_name(hw->wiphy), MWL8K_TX_WAIT_TIMEOUT_MS);
|
|
|
+ mwl8k_dump_tx_rings(hw);
|
|
|
|
|
|
- return -ETIMEDOUT;
|
|
|
+ rc = -ETIMEDOUT;
|
|
|
}
|
|
|
+ spin_unlock_bh(&priv->tx_lock);
|
|
|
|
|
|
- return 0;
|
|
|
+ return rc;
|
|
|
}
|
|
|
|
|
|
#define MWL8K_TXD_SUCCESS(status) \
|