Commit c41cae06 authored by Felix Fietkau's avatar Felix Fietkau Committed by Greg Kroah-Hartman

ath9k: fix race condition in enabling/disabling IRQs

commit 3a5e969b upstream.

The code currently relies on refcounting to disable IRQs from within the
IRQ handler and re-enabling them again after the tasklet has run.

However, due to race conditions sometimes the IRQ handler might be
called twice, or the tasklet may not run at all (if interrupted in the
middle of a reset).

This can cause nasty imbalances in the irq-disable refcount which will
get the driver permanently stuck until the entire radio has been stopped
and started again (ath_reset will not recover from this).

Instead of using this fragile logic, change the code to ensure that
running the irq handler during tasklet processing is safe, and leave the
refcount untouched.
Signed-off-by: default avatarFelix Fietkau <nbd@nbd.name>
Signed-off-by: default avatarKalle Valo <kvalo@qca.qualcomm.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 93c1f1db
...@@ -959,6 +959,7 @@ struct ath_softc { ...@@ -959,6 +959,7 @@ struct ath_softc {
struct survey_info *cur_survey; struct survey_info *cur_survey;
struct survey_info survey[ATH9K_NUM_CHANNELS]; struct survey_info survey[ATH9K_NUM_CHANNELS];
spinlock_t intr_lock;
struct tasklet_struct intr_tq; struct tasklet_struct intr_tq;
struct tasklet_struct bcon_tasklet; struct tasklet_struct bcon_tasklet;
struct ath_hw *sc_ah; struct ath_hw *sc_ah;
......
...@@ -626,6 +626,7 @@ static int ath9k_init_softc(u16 devid, struct ath_softc *sc, ...@@ -626,6 +626,7 @@ static int ath9k_init_softc(u16 devid, struct ath_softc *sc,
common->bt_ant_diversity = 1; common->bt_ant_diversity = 1;
spin_lock_init(&common->cc_lock); spin_lock_init(&common->cc_lock);
spin_lock_init(&sc->intr_lock);
spin_lock_init(&sc->sc_serial_rw); spin_lock_init(&sc->sc_serial_rw);
spin_lock_init(&sc->sc_pm_lock); spin_lock_init(&sc->sc_pm_lock);
spin_lock_init(&sc->chan_lock); spin_lock_init(&sc->chan_lock);
......
...@@ -805,21 +805,12 @@ void ath9k_hw_disable_interrupts(struct ath_hw *ah) ...@@ -805,21 +805,12 @@ void ath9k_hw_disable_interrupts(struct ath_hw *ah)
} }
EXPORT_SYMBOL(ath9k_hw_disable_interrupts); EXPORT_SYMBOL(ath9k_hw_disable_interrupts);
void ath9k_hw_enable_interrupts(struct ath_hw *ah) static void __ath9k_hw_enable_interrupts(struct ath_hw *ah)
{ {
struct ath_common *common = ath9k_hw_common(ah); struct ath_common *common = ath9k_hw_common(ah);
u32 sync_default = AR_INTR_SYNC_DEFAULT; u32 sync_default = AR_INTR_SYNC_DEFAULT;
u32 async_mask; u32 async_mask;
if (!(ah->imask & ATH9K_INT_GLOBAL))
return;
if (!atomic_inc_and_test(&ah->intr_ref_cnt)) {
ath_dbg(common, INTERRUPT, "Do not enable IER ref count %d\n",
atomic_read(&ah->intr_ref_cnt));
return;
}
if (AR_SREV_9340(ah) || AR_SREV_9550(ah) || AR_SREV_9531(ah) || if (AR_SREV_9340(ah) || AR_SREV_9550(ah) || AR_SREV_9531(ah) ||
AR_SREV_9561(ah)) AR_SREV_9561(ah))
sync_default &= ~AR_INTR_SYNC_HOST1_FATAL; sync_default &= ~AR_INTR_SYNC_HOST1_FATAL;
...@@ -841,6 +832,39 @@ void ath9k_hw_enable_interrupts(struct ath_hw *ah) ...@@ -841,6 +832,39 @@ void ath9k_hw_enable_interrupts(struct ath_hw *ah)
ath_dbg(common, INTERRUPT, "AR_IMR 0x%x IER 0x%x\n", ath_dbg(common, INTERRUPT, "AR_IMR 0x%x IER 0x%x\n",
REG_READ(ah, AR_IMR), REG_READ(ah, AR_IER)); REG_READ(ah, AR_IMR), REG_READ(ah, AR_IER));
} }
void ath9k_hw_resume_interrupts(struct ath_hw *ah)
{
struct ath_common *common = ath9k_hw_common(ah);
if (!(ah->imask & ATH9K_INT_GLOBAL))
return;
if (atomic_read(&ah->intr_ref_cnt) != 0) {
ath_dbg(common, INTERRUPT, "Do not enable IER ref count %d\n",
atomic_read(&ah->intr_ref_cnt));
return;
}
__ath9k_hw_enable_interrupts(ah);
}
EXPORT_SYMBOL(ath9k_hw_resume_interrupts);
void ath9k_hw_enable_interrupts(struct ath_hw *ah)
{
struct ath_common *common = ath9k_hw_common(ah);
if (!(ah->imask & ATH9K_INT_GLOBAL))
return;
if (!atomic_inc_and_test(&ah->intr_ref_cnt)) {
ath_dbg(common, INTERRUPT, "Do not enable IER ref count %d\n",
atomic_read(&ah->intr_ref_cnt));
return;
}
__ath9k_hw_enable_interrupts(ah);
}
EXPORT_SYMBOL(ath9k_hw_enable_interrupts); EXPORT_SYMBOL(ath9k_hw_enable_interrupts);
void ath9k_hw_set_interrupts(struct ath_hw *ah) void ath9k_hw_set_interrupts(struct ath_hw *ah)
......
...@@ -744,6 +744,7 @@ void ath9k_hw_set_interrupts(struct ath_hw *ah); ...@@ -744,6 +744,7 @@ void ath9k_hw_set_interrupts(struct ath_hw *ah);
void ath9k_hw_enable_interrupts(struct ath_hw *ah); void ath9k_hw_enable_interrupts(struct ath_hw *ah);
void ath9k_hw_disable_interrupts(struct ath_hw *ah); void ath9k_hw_disable_interrupts(struct ath_hw *ah);
void ath9k_hw_kill_interrupts(struct ath_hw *ah); void ath9k_hw_kill_interrupts(struct ath_hw *ah);
void ath9k_hw_resume_interrupts(struct ath_hw *ah);
void ar9002_hw_attach_mac_ops(struct ath_hw *ah); void ar9002_hw_attach_mac_ops(struct ath_hw *ah);
......
...@@ -373,21 +373,20 @@ void ath9k_tasklet(unsigned long data) ...@@ -373,21 +373,20 @@ void ath9k_tasklet(unsigned long data)
struct ath_common *common = ath9k_hw_common(ah); struct ath_common *common = ath9k_hw_common(ah);
enum ath_reset_type type; enum ath_reset_type type;
unsigned long flags; unsigned long flags;
u32 status = sc->intrstatus; u32 status;
u32 rxmask; u32 rxmask;
spin_lock_irqsave(&sc->intr_lock, flags);
status = sc->intrstatus;
sc->intrstatus = 0;
spin_unlock_irqrestore(&sc->intr_lock, flags);
ath9k_ps_wakeup(sc); ath9k_ps_wakeup(sc);
spin_lock(&sc->sc_pcu_lock); spin_lock(&sc->sc_pcu_lock);
if (status & ATH9K_INT_FATAL) { if (status & ATH9K_INT_FATAL) {
type = RESET_TYPE_FATAL_INT; type = RESET_TYPE_FATAL_INT;
ath9k_queue_reset(sc, type); ath9k_queue_reset(sc, type);
/*
* Increment the ref. counter here so that
* interrupts are enabled in the reset routine.
*/
atomic_inc(&ah->intr_ref_cnt);
ath_dbg(common, RESET, "FATAL: Skipping interrupts\n"); ath_dbg(common, RESET, "FATAL: Skipping interrupts\n");
goto out; goto out;
} }
...@@ -403,11 +402,6 @@ void ath9k_tasklet(unsigned long data) ...@@ -403,11 +402,6 @@ void ath9k_tasklet(unsigned long data)
type = RESET_TYPE_BB_WATCHDOG; type = RESET_TYPE_BB_WATCHDOG;
ath9k_queue_reset(sc, type); ath9k_queue_reset(sc, type);
/*
* Increment the ref. counter here so that
* interrupts are enabled in the reset routine.
*/
atomic_inc(&ah->intr_ref_cnt);
ath_dbg(common, RESET, ath_dbg(common, RESET,
"BB_WATCHDOG: Skipping interrupts\n"); "BB_WATCHDOG: Skipping interrupts\n");
goto out; goto out;
...@@ -420,7 +414,6 @@ void ath9k_tasklet(unsigned long data) ...@@ -420,7 +414,6 @@ void ath9k_tasklet(unsigned long data)
if ((sc->gtt_cnt >= MAX_GTT_CNT) && !ath9k_hw_check_alive(ah)) { if ((sc->gtt_cnt >= MAX_GTT_CNT) && !ath9k_hw_check_alive(ah)) {
type = RESET_TYPE_TX_GTT; type = RESET_TYPE_TX_GTT;
ath9k_queue_reset(sc, type); ath9k_queue_reset(sc, type);
atomic_inc(&ah->intr_ref_cnt);
ath_dbg(common, RESET, ath_dbg(common, RESET,
"GTT: Skipping interrupts\n"); "GTT: Skipping interrupts\n");
goto out; goto out;
...@@ -477,7 +470,7 @@ void ath9k_tasklet(unsigned long data) ...@@ -477,7 +470,7 @@ void ath9k_tasklet(unsigned long data)
ath9k_btcoex_handle_interrupt(sc, status); ath9k_btcoex_handle_interrupt(sc, status);
/* re-enable hardware interrupt */ /* re-enable hardware interrupt */
ath9k_hw_enable_interrupts(ah); ath9k_hw_resume_interrupts(ah);
out: out:
spin_unlock(&sc->sc_pcu_lock); spin_unlock(&sc->sc_pcu_lock);
ath9k_ps_restore(sc); ath9k_ps_restore(sc);
...@@ -541,7 +534,9 @@ irqreturn_t ath_isr(int irq, void *dev) ...@@ -541,7 +534,9 @@ irqreturn_t ath_isr(int irq, void *dev)
return IRQ_NONE; return IRQ_NONE;
/* Cache the status */ /* Cache the status */
sc->intrstatus = status; spin_lock(&sc->intr_lock);
sc->intrstatus |= status;
spin_unlock(&sc->intr_lock);
if (status & SCHED_INTR) if (status & SCHED_INTR)
sched = true; sched = true;
...@@ -587,7 +582,7 @@ irqreturn_t ath_isr(int irq, void *dev) ...@@ -587,7 +582,7 @@ irqreturn_t ath_isr(int irq, void *dev)
if (sched) { if (sched) {
/* turn off every interrupt */ /* turn off every interrupt */
ath9k_hw_disable_interrupts(ah); ath9k_hw_kill_interrupts(ah);
tasklet_schedule(&sc->intr_tq); tasklet_schedule(&sc->intr_tq);
} }
......
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