mirror of
https://github.com/rd-stuffs/msm-4.14.git
synced 2025-02-20 11:45:48 +08:00
iwlwifi: pcie: fix a race in firmware loading flow
Upon firmware load interrupt (FH_TX), the ISR re-enables the firmware load interrupt only to avoid races with other flows as described in the commit below. When the firmware is completely loaded, the thread that is loading the firmware will enable all the interrupts to make sure that the driver gets the ALIVE interrupt. The problem with that is that the thread that is loading the firmware is actually racing against the ISR and we can get to the following situation: CPU0 CPU1 iwl_pcie_load_given_ucode ... iwl_pcie_load_firmware_chunk wait_for_interrupt <interrupt> ISR handles CSR_INT_BIT_FH_TX ISR wakes up the thread on CPU0 /* enable all the interrupts * to get the ALIVE interrupt */ iwl_enable_interrupts ISR re-enables CSR_INT_BIT_FH_TX only /* start the firmware */ iwl_write32(trans, CSR_RESET, 0); BUG! ALIVE interrupt will never arrive since it has been masked by CPU1. In order to fix that, change the ISR to first check if STATUS_INT_ENABLED is set. If so, re-enable all the interrupts. If STATUS_INT_ENABLED is clear, then we can check what specific interrupt happened and re-enable only that specific interrupt (RFKILL or FH_TX). All the credit for the analysis goes to Kirtika who did the actual debugging work. Cc: <stable@vger.kernel.org> [4.5+] Fixes: a6bd005fe92 ("iwlwifi: pcie: fix RF-Kill vs. firmware load race") Signed-off-by: Luca Coelho <luciano.coelho@intel.com>
This commit is contained in:
parent
21cb3222fe
commit
f16c3ebfa6
@ -500,7 +500,7 @@ void iwl_pcie_dump_csr(struct iwl_trans *trans);
|
||||
/*****************************************************
|
||||
* Helpers
|
||||
******************************************************/
|
||||
static inline void iwl_disable_interrupts(struct iwl_trans *trans)
|
||||
static inline void _iwl_disable_interrupts(struct iwl_trans *trans)
|
||||
{
|
||||
struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
|
||||
|
||||
@ -523,7 +523,16 @@ static inline void iwl_disable_interrupts(struct iwl_trans *trans)
|
||||
IWL_DEBUG_ISR(trans, "Disabled interrupts\n");
|
||||
}
|
||||
|
||||
static inline void iwl_enable_interrupts(struct iwl_trans *trans)
|
||||
static inline void iwl_disable_interrupts(struct iwl_trans *trans)
|
||||
{
|
||||
struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
|
||||
|
||||
spin_lock(&trans_pcie->irq_lock);
|
||||
_iwl_disable_interrupts(trans);
|
||||
spin_unlock(&trans_pcie->irq_lock);
|
||||
}
|
||||
|
||||
static inline void _iwl_enable_interrupts(struct iwl_trans *trans)
|
||||
{
|
||||
struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
|
||||
|
||||
@ -546,6 +555,14 @@ static inline void iwl_enable_interrupts(struct iwl_trans *trans)
|
||||
}
|
||||
}
|
||||
|
||||
static inline void iwl_enable_interrupts(struct iwl_trans *trans)
|
||||
{
|
||||
struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
|
||||
|
||||
spin_lock(&trans_pcie->irq_lock);
|
||||
_iwl_enable_interrupts(trans);
|
||||
spin_unlock(&trans_pcie->irq_lock);
|
||||
}
|
||||
static inline void iwl_enable_hw_int_msk_msix(struct iwl_trans *trans, u32 msk)
|
||||
{
|
||||
struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
|
||||
|
@ -1535,7 +1535,7 @@ irqreturn_t iwl_pcie_irq_handler(int irq, void *dev_id)
|
||||
* have anything to service
|
||||
*/
|
||||
if (test_bit(STATUS_INT_ENABLED, &trans->status))
|
||||
iwl_enable_interrupts(trans);
|
||||
_iwl_enable_interrupts(trans);
|
||||
spin_unlock(&trans_pcie->irq_lock);
|
||||
lock_map_release(&trans->sync_cmd_lockdep_map);
|
||||
return IRQ_NONE;
|
||||
@ -1727,15 +1727,17 @@ irqreturn_t iwl_pcie_irq_handler(int irq, void *dev_id)
|
||||
inta & ~trans_pcie->inta_mask);
|
||||
}
|
||||
|
||||
/* we are loading the firmware, enable FH_TX interrupt only */
|
||||
if (handled & CSR_INT_BIT_FH_TX)
|
||||
iwl_enable_fw_load_int(trans);
|
||||
spin_lock(&trans_pcie->irq_lock);
|
||||
/* only Re-enable all interrupt if disabled by irq */
|
||||
else if (test_bit(STATUS_INT_ENABLED, &trans->status))
|
||||
iwl_enable_interrupts(trans);
|
||||
if (test_bit(STATUS_INT_ENABLED, &trans->status))
|
||||
_iwl_enable_interrupts(trans);
|
||||
/* we are loading the firmware, enable FH_TX interrupt only */
|
||||
else if (handled & CSR_INT_BIT_FH_TX)
|
||||
iwl_enable_fw_load_int(trans);
|
||||
/* Re-enable RF_KILL if it occurred */
|
||||
else if (handled & CSR_INT_BIT_RF_KILL)
|
||||
iwl_enable_rfkill_int(trans);
|
||||
spin_unlock(&trans_pcie->irq_lock);
|
||||
|
||||
out:
|
||||
lock_map_release(&trans->sync_cmd_lockdep_map);
|
||||
@ -1799,7 +1801,7 @@ void iwl_pcie_reset_ict(struct iwl_trans *trans)
|
||||
return;
|
||||
|
||||
spin_lock(&trans_pcie->irq_lock);
|
||||
iwl_disable_interrupts(trans);
|
||||
_iwl_disable_interrupts(trans);
|
||||
|
||||
memset(trans_pcie->ict_tbl, 0, ICT_SIZE);
|
||||
|
||||
@ -1815,7 +1817,7 @@ void iwl_pcie_reset_ict(struct iwl_trans *trans)
|
||||
trans_pcie->use_ict = true;
|
||||
trans_pcie->ict_index = 0;
|
||||
iwl_write32(trans, CSR_INT, trans_pcie->inta_mask);
|
||||
iwl_enable_interrupts(trans);
|
||||
_iwl_enable_interrupts(trans);
|
||||
spin_unlock(&trans_pcie->irq_lock);
|
||||
}
|
||||
|
||||
|
@ -1037,9 +1037,7 @@ static void _iwl_trans_pcie_stop_device(struct iwl_trans *trans, bool low_power)
|
||||
was_hw_rfkill = iwl_is_rfkill_set(trans);
|
||||
|
||||
/* tell the device to stop sending interrupts */
|
||||
spin_lock(&trans_pcie->irq_lock);
|
||||
iwl_disable_interrupts(trans);
|
||||
spin_unlock(&trans_pcie->irq_lock);
|
||||
|
||||
/* device going down, Stop using ICT table */
|
||||
iwl_pcie_disable_ict(trans);
|
||||
@ -1083,9 +1081,7 @@ static void _iwl_trans_pcie_stop_device(struct iwl_trans *trans, bool low_power)
|
||||
* the time, unless the interrupt is ACKed even if the interrupt
|
||||
* should be masked. Re-ACK all the interrupts here.
|
||||
*/
|
||||
spin_lock(&trans_pcie->irq_lock);
|
||||
iwl_disable_interrupts(trans);
|
||||
spin_unlock(&trans_pcie->irq_lock);
|
||||
|
||||
/* clear all status bits */
|
||||
clear_bit(STATUS_SYNC_HCMD_ACTIVE, &trans->status);
|
||||
@ -1578,15 +1574,11 @@ static void iwl_trans_pcie_op_mode_leave(struct iwl_trans *trans)
|
||||
mutex_lock(&trans_pcie->mutex);
|
||||
|
||||
/* disable interrupts - don't enable HW RF kill interrupt */
|
||||
spin_lock(&trans_pcie->irq_lock);
|
||||
iwl_disable_interrupts(trans);
|
||||
spin_unlock(&trans_pcie->irq_lock);
|
||||
|
||||
iwl_pcie_apm_stop(trans, true);
|
||||
|
||||
spin_lock(&trans_pcie->irq_lock);
|
||||
iwl_disable_interrupts(trans);
|
||||
spin_unlock(&trans_pcie->irq_lock);
|
||||
|
||||
iwl_pcie_disable_ict(trans);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user