iwlwifi: pcie: speed up the Tx DMA stop flow
authorEmmanuel Grumbach <emmanuel.grumbach@intel.com>
Wed, 25 Feb 2015 13:49:39 +0000 (15:49 +0200)
committerEmmanuel Grumbach <emmanuel.grumbach@intel.com>
Thu, 12 Mar 2015 07:57:35 +0000 (09:57 +0200)
We don't need to acquire MAC access for each access, it
makes much more sense to keep the MAC access. This speeds
up the Tx DMA stop flow significantly.
Moreover, if one channel can't be stopped, stop the others
but don't poll for them to avoid being stuck there for a
long time.

This solves a situation in which we were stuck in that flow
for way too long with a spinlock held which led to a kernel
panic.

Reviewed-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Emmanuel Grumbach <emmanuel.grumbach@intel.com>
drivers/net/wireless/iwlwifi/pcie/tx.c

index af0bce7..26e6dd0 100644 (file)
@@ -725,33 +725,50 @@ void iwl_trans_pcie_tx_reset(struct iwl_trans *trans)
        iwl_pcie_tx_start(trans, 0);
 }
 
+static void iwl_pcie_tx_stop_fh(struct iwl_trans *trans)
+{
+       struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
+       unsigned long flags;
+       int ch, ret;
+       u32 mask = 0;
+
+       spin_lock(&trans_pcie->irq_lock);
+
+       if (!iwl_trans_grab_nic_access(trans, false, &flags))
+               goto out;
+
+       /* Stop each Tx DMA channel */
+       for (ch = 0; ch < FH_TCSR_CHNL_NUM; ch++) {
+               iwl_write32(trans, FH_TCSR_CHNL_TX_CONFIG_REG(ch), 0x0);
+               mask |= FH_TSSR_TX_STATUS_REG_MSK_CHNL_IDLE(ch);
+       }
+
+       /* Wait for DMA channels to be idle */
+       ret = iwl_poll_bit(trans, FH_TSSR_TX_STATUS_REG, mask, mask, 5000);
+       if (ret < 0)
+               IWL_ERR(trans,
+                       "Failing on timeout while stopping DMA channel %d [0x%08x]\n",
+                       ch, iwl_read32(trans, FH_TSSR_TX_STATUS_REG));
+
+       iwl_trans_release_nic_access(trans, &flags);
+
+out:
+       spin_unlock(&trans_pcie->irq_lock);
+}
+
 /*
  * iwl_pcie_tx_stop - Stop all Tx DMA channels
  */
 int iwl_pcie_tx_stop(struct iwl_trans *trans)
 {
        struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
-       int ch, txq_id, ret;
+       int txq_id;
 
        /* Turn off all Tx DMA fifos */
-       spin_lock(&trans_pcie->irq_lock);
-
        iwl_scd_deactivate_fifos(trans);
 
-       /* Stop each Tx DMA channel, and wait for it to be idle */
-       for (ch = 0; ch < FH_TCSR_CHNL_NUM; ch++) {
-               iwl_write_direct32(trans,
-                                  FH_TCSR_CHNL_TX_CONFIG_REG(ch), 0x0);
-               ret = iwl_poll_direct_bit(trans, FH_TSSR_TX_STATUS_REG,
-                       FH_TSSR_TX_STATUS_REG_MSK_CHNL_IDLE(ch), 1000);
-               if (ret < 0)
-                       IWL_ERR(trans,
-                               "Failing on timeout while stopping DMA channel %d [0x%08x]\n",
-                               ch,
-                               iwl_read_direct32(trans,
-                                                 FH_TSSR_TX_STATUS_REG));
-       }
-       spin_unlock(&trans_pcie->irq_lock);
+       /* Turn off all Tx DMA channels */
+       iwl_pcie_tx_stop_fh(trans);
 
        /*
         * This function can be called before the op_mode disabled the