iwlwifi: pcie: keep the NIC awake when commands are in flight

Under very specific circumstances, the firmware might
ignore a host command. This was debugged and we ended up
seeing that the power management hardware was faulty.
In order to workaround this issue, we keep the NIC awake
as long as we have host commands in flight. This will avoid
to put the hardware into buggy condition.

Signed-off-by: Emmanuel Grumbach <emmanuel.grumbach@intel.com>
This commit is contained in:
Emmanuel Grumbach 2013-12-22 15:09:40 +02:00
parent a4a1247847
commit b943949105
3 changed files with 82 additions and 53 deletions

View File

@ -262,6 +262,7 @@ iwl_pcie_get_scratchbuf_dma(struct iwl_txq *txq, int idx)
* @rx_page_order: page order for receive buffer size * @rx_page_order: page order for receive buffer size
* @wd_timeout: queue watchdog timeout (jiffies) * @wd_timeout: queue watchdog timeout (jiffies)
* @reg_lock: protect hw register access * @reg_lock: protect hw register access
* @cmd_in_flight: true when we have a host command in flight
*/ */
struct iwl_trans_pcie { struct iwl_trans_pcie {
struct iwl_rxq rxq; struct iwl_rxq rxq;
@ -310,6 +311,7 @@ struct iwl_trans_pcie {
/*protect hw register */ /*protect hw register */
spinlock_t reg_lock; spinlock_t reg_lock;
bool cmd_in_flight;
}; };
#define IWL_TRANS_GET_PCIE_TRANS(_iwl_trans) \ #define IWL_TRANS_GET_PCIE_TRANS(_iwl_trans) \
@ -459,4 +461,31 @@ static inline bool iwl_is_rfkill_set(struct iwl_trans *trans)
CSR_GP_CNTRL_REG_FLAG_HW_RF_KILL_SW); CSR_GP_CNTRL_REG_FLAG_HW_RF_KILL_SW);
} }
static inline void __iwl_trans_pcie_set_bits_mask(struct iwl_trans *trans,
u32 reg, u32 mask, u32 value)
{
u32 v;
#ifdef CONFIG_IWLWIFI_DEBUG
WARN_ON_ONCE(value & ~mask);
#endif
v = iwl_read32(trans, reg);
v &= ~mask;
v |= value;
iwl_write32(trans, reg, v);
}
static inline void __iwl_trans_pcie_clear_bit(struct iwl_trans *trans,
u32 reg, u32 mask)
{
__iwl_trans_pcie_set_bits_mask(trans, reg, mask, 0);
}
static inline void __iwl_trans_pcie_set_bit(struct iwl_trans *trans,
u32 reg, u32 mask)
{
__iwl_trans_pcie_set_bits_mask(trans, reg, mask, mask);
}
#endif /* __iwl_trans_int_pcie_h__ */ #endif /* __iwl_trans_int_pcie_h__ */

View File

@ -75,33 +75,6 @@
#include "iwl-agn-hw.h" #include "iwl-agn-hw.h"
#include "internal.h" #include "internal.h"
static void __iwl_trans_pcie_set_bits_mask(struct iwl_trans *trans,
u32 reg, u32 mask, u32 value)
{
u32 v;
#ifdef CONFIG_IWLWIFI_DEBUG
WARN_ON_ONCE(value & ~mask);
#endif
v = iwl_read32(trans, reg);
v &= ~mask;
v |= value;
iwl_write32(trans, reg, v);
}
static inline void __iwl_trans_pcie_clear_bit(struct iwl_trans *trans,
u32 reg, u32 mask)
{
__iwl_trans_pcie_set_bits_mask(trans, reg, mask, 0);
}
static inline void __iwl_trans_pcie_set_bit(struct iwl_trans *trans,
u32 reg, u32 mask)
{
__iwl_trans_pcie_set_bits_mask(trans, reg, mask, mask);
}
static void iwl_pcie_set_pwr(struct iwl_trans *trans, bool vaux) static void iwl_pcie_set_pwr(struct iwl_trans *trans, bool vaux)
{ {
if (vaux && pci_pme_capable(to_pci_dev(trans->dev), PCI_D3cold)) if (vaux && pci_pme_capable(to_pci_dev(trans->dev), PCI_D3cold))
@ -929,6 +902,9 @@ static bool iwl_trans_pcie_grab_nic_access(struct iwl_trans *trans, bool silent,
spin_lock_irqsave(&trans_pcie->reg_lock, *flags); spin_lock_irqsave(&trans_pcie->reg_lock, *flags);
if (trans_pcie->cmd_in_flight)
goto out;
/* this bit wakes up the NIC */ /* this bit wakes up the NIC */
__iwl_trans_pcie_set_bit(trans, CSR_GP_CNTRL, __iwl_trans_pcie_set_bit(trans, CSR_GP_CNTRL,
CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ);
@ -968,6 +944,7 @@ static bool iwl_trans_pcie_grab_nic_access(struct iwl_trans *trans, bool silent,
} }
} }
out:
/* /*
* Fool sparse by faking we release the lock - sparse will * Fool sparse by faking we release the lock - sparse will
* track nic_access anyway. * track nic_access anyway.
@ -989,6 +966,9 @@ static void iwl_trans_pcie_release_nic_access(struct iwl_trans *trans,
*/ */
__acquire(&trans_pcie->reg_lock); __acquire(&trans_pcie->reg_lock);
if (trans_pcie->cmd_in_flight)
goto out;
__iwl_trans_pcie_clear_bit(trans, CSR_GP_CNTRL, __iwl_trans_pcie_clear_bit(trans, CSR_GP_CNTRL,
CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ);
/* /*
@ -998,6 +978,7 @@ static void iwl_trans_pcie_release_nic_access(struct iwl_trans *trans,
* scheduled on different CPUs (after we drop reg_lock). * scheduled on different CPUs (after we drop reg_lock).
*/ */
mmiowb(); mmiowb();
out:
spin_unlock_irqrestore(&trans_pcie->reg_lock, *flags); spin_unlock_irqrestore(&trans_pcie->reg_lock, *flags);
} }

View File

@ -1001,6 +1001,7 @@ static void iwl_pcie_cmdq_reclaim(struct iwl_trans *trans, int txq_id, int idx)
struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
struct iwl_txq *txq = &trans_pcie->txq[txq_id]; struct iwl_txq *txq = &trans_pcie->txq[txq_id];
struct iwl_queue *q = &txq->q; struct iwl_queue *q = &txq->q;
unsigned long flags;
int nfreed = 0; int nfreed = 0;
lockdep_assert_held(&txq->lock); lockdep_assert_held(&txq->lock);
@ -1023,6 +1024,16 @@ static void iwl_pcie_cmdq_reclaim(struct iwl_trans *trans, int txq_id, int idx)
} }
} }
if (q->read_ptr == q->write_ptr) {
spin_lock_irqsave(&trans_pcie->reg_lock, flags);
WARN_ON(!trans_pcie->cmd_in_flight);
trans_pcie->cmd_in_flight = false;
__iwl_trans_pcie_clear_bit(trans,
CSR_GP_CNTRL,
CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ);
spin_unlock_irqrestore(&trans_pcie->reg_lock, flags);
}
iwl_pcie_txq_progress(trans_pcie, txq); iwl_pcie_txq_progress(trans_pcie, txq);
} }
@ -1174,12 +1185,13 @@ static int iwl_pcie_enqueue_hcmd(struct iwl_trans *trans,
struct iwl_queue *q = &txq->q; struct iwl_queue *q = &txq->q;
struct iwl_device_cmd *out_cmd; struct iwl_device_cmd *out_cmd;
struct iwl_cmd_meta *out_meta; struct iwl_cmd_meta *out_meta;
unsigned long flags;
void *dup_buf = NULL; void *dup_buf = NULL;
dma_addr_t phys_addr; dma_addr_t phys_addr;
int idx; int idx;
u16 copy_size, cmd_size, scratch_size; u16 copy_size, cmd_size, scratch_size;
bool had_nocopy = false; bool had_nocopy = false;
int i; int i, ret;
u32 cmd_pos; u32 cmd_pos;
const u8 *cmddata[IWL_MAX_CMD_TBS_PER_TFD]; const u8 *cmddata[IWL_MAX_CMD_TBS_PER_TFD];
u16 cmdlen[IWL_MAX_CMD_TBS_PER_TFD]; u16 cmdlen[IWL_MAX_CMD_TBS_PER_TFD];
@ -1377,10 +1389,38 @@ static int iwl_pcie_enqueue_hcmd(struct iwl_trans *trans,
if (q->read_ptr == q->write_ptr && trans_pcie->wd_timeout) if (q->read_ptr == q->write_ptr && trans_pcie->wd_timeout)
mod_timer(&txq->stuck_timer, jiffies + trans_pcie->wd_timeout); mod_timer(&txq->stuck_timer, jiffies + trans_pcie->wd_timeout);
spin_lock_irqsave(&trans_pcie->reg_lock, flags);
/*
* wake up the NIC to make sure that the firmware will see the host
* command - we will let the NIC sleep once all the host commands
* returned.
*/
if (!trans_pcie->cmd_in_flight) {
trans_pcie->cmd_in_flight = true;
__iwl_trans_pcie_set_bit(trans, CSR_GP_CNTRL,
CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ);
ret = iwl_poll_bit(trans, CSR_GP_CNTRL,
CSR_GP_CNTRL_REG_VAL_MAC_ACCESS_EN,
(CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY |
CSR_GP_CNTRL_REG_FLAG_GOING_TO_SLEEP),
15000);
if (ret < 0) {
__iwl_trans_pcie_clear_bit(trans, CSR_GP_CNTRL,
CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ);
spin_unlock_irqrestore(&trans_pcie->reg_lock, flags);
trans_pcie->cmd_in_flight = false;
idx = -EIO;
goto out;
}
}
/* Increment and update queue's write index */ /* Increment and update queue's write index */
q->write_ptr = iwl_queue_inc_wrap(q->write_ptr, q->n_bd); q->write_ptr = iwl_queue_inc_wrap(q->write_ptr, q->n_bd);
iwl_pcie_txq_inc_wr_ptr(trans, txq); iwl_pcie_txq_inc_wr_ptr(trans, txq);
spin_unlock_irqrestore(&trans_pcie->reg_lock, flags);
out: out:
spin_unlock_bh(&txq->lock); spin_unlock_bh(&txq->lock);
free_dup_buf: free_dup_buf:
@ -1462,7 +1502,6 @@ void iwl_pcie_hcmd_complete(struct iwl_trans *trans,
} }
#define HOST_COMPLETE_TIMEOUT (2 * HZ) #define HOST_COMPLETE_TIMEOUT (2 * HZ)
#define COMMAND_POKE_TIMEOUT (HZ / 10)
static int iwl_pcie_send_hcmd_async(struct iwl_trans *trans, static int iwl_pcie_send_hcmd_async(struct iwl_trans *trans,
struct iwl_host_cmd *cmd) struct iwl_host_cmd *cmd)
@ -1490,7 +1529,6 @@ static int iwl_pcie_send_hcmd_sync(struct iwl_trans *trans,
struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
int cmd_idx; int cmd_idx;
int ret; int ret;
int timeout = HOST_COMPLETE_TIMEOUT;
IWL_DEBUG_INFO(trans, "Attempting to send sync command %s\n", IWL_DEBUG_INFO(trans, "Attempting to send sync command %s\n",
get_cmd_string(trans_pcie, cmd->id)); get_cmd_string(trans_pcie, cmd->id));
@ -1514,29 +1552,10 @@ static int iwl_pcie_send_hcmd_sync(struct iwl_trans *trans,
return ret; return ret;
} }
while (timeout > 0) { ret = wait_event_timeout(trans_pcie->wait_command_queue,
unsigned long flags; !test_bit(STATUS_SYNC_HCMD_ACTIVE,
&trans->status),
timeout -= COMMAND_POKE_TIMEOUT; HOST_COMPLETE_TIMEOUT);
ret = wait_event_timeout(trans_pcie->wait_command_queue,
!test_bit(STATUS_SYNC_HCMD_ACTIVE,
&trans->status),
COMMAND_POKE_TIMEOUT);
if (ret)
break;
/* poke the device - it may have lost the command */
if (iwl_trans_grab_nic_access(trans, true, &flags)) {
iwl_trans_release_nic_access(trans, &flags);
IWL_DEBUG_INFO(trans,
"Tried to wake NIC for command %s\n",
get_cmd_string(trans_pcie, cmd->id));
} else {
IWL_ERR(trans, "Failed to poke NIC for command %s\n",
get_cmd_string(trans_pcie, cmd->id));
break;
}
}
if (!ret) { if (!ret) {
struct iwl_txq *txq = &trans_pcie->txq[trans_pcie->cmd_queue]; struct iwl_txq *txq = &trans_pcie->txq[trans_pcie->cmd_queue];
struct iwl_queue *q = &txq->q; struct iwl_queue *q = &txq->q;