aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/net/wireless
diff options
context:
space:
mode:
authorFelix Fietkau <nbd@nbd.name>2017-02-02 04:14:52 -0500
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2017-03-12 00:41:46 -0500
commitc41cae06bf6027d2f77ebbb12bc17da1cd6e131f (patch)
tree4cff363c691f8e4ef4b41f503f25657ef48f959a /drivers/net/wireless
parent93c1f1db1a7421545e9aae9fb6091668ca570f2b (diff)
ath9k: fix race condition in enabling/disabling IRQs
commit 3a5e969bb2f6692a256352649355d56d018d6b88 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: Felix Fietkau <nbd@nbd.name> Signed-off-by: Kalle Valo <kvalo@qca.qualcomm.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/net/wireless')
-rw-r--r--drivers/net/wireless/ath/ath9k/ath9k.h1
-rw-r--r--drivers/net/wireless/ath/ath9k/init.c1
-rw-r--r--drivers/net/wireless/ath/ath9k/mac.c44
-rw-r--r--drivers/net/wireless/ath/ath9k/mac.h1
-rw-r--r--drivers/net/wireless/ath/ath9k/main.c27
5 files changed, 48 insertions, 26 deletions
diff --git a/drivers/net/wireless/ath/ath9k/ath9k.h b/drivers/net/wireless/ath/ath9k/ath9k.h
index 26fc8ecfe8c4..a7316710a902 100644
--- a/drivers/net/wireless/ath/ath9k/ath9k.h
+++ b/drivers/net/wireless/ath/ath9k/ath9k.h
@@ -959,6 +959,7 @@ struct ath_softc {
959 struct survey_info *cur_survey; 959 struct survey_info *cur_survey;
960 struct survey_info survey[ATH9K_NUM_CHANNELS]; 960 struct survey_info survey[ATH9K_NUM_CHANNELS];
961 961
962 spinlock_t intr_lock;
962 struct tasklet_struct intr_tq; 963 struct tasklet_struct intr_tq;
963 struct tasklet_struct bcon_tasklet; 964 struct tasklet_struct bcon_tasklet;
964 struct ath_hw *sc_ah; 965 struct ath_hw *sc_ah;
diff --git a/drivers/net/wireless/ath/ath9k/init.c b/drivers/net/wireless/ath/ath9k/init.c
index cfa3fe82ade3..297d4bbc5c05 100644
--- a/drivers/net/wireless/ath/ath9k/init.c
+++ b/drivers/net/wireless/ath/ath9k/init.c
@@ -626,6 +626,7 @@ static int ath9k_init_softc(u16 devid, struct ath_softc *sc,
626 common->bt_ant_diversity = 1; 626 common->bt_ant_diversity = 1;
627 627
628 spin_lock_init(&common->cc_lock); 628 spin_lock_init(&common->cc_lock);
629 spin_lock_init(&sc->intr_lock);
629 spin_lock_init(&sc->sc_serial_rw); 630 spin_lock_init(&sc->sc_serial_rw);
630 spin_lock_init(&sc->sc_pm_lock); 631 spin_lock_init(&sc->sc_pm_lock);
631 spin_lock_init(&sc->chan_lock); 632 spin_lock_init(&sc->chan_lock);
diff --git a/drivers/net/wireless/ath/ath9k/mac.c b/drivers/net/wireless/ath/ath9k/mac.c
index bba85d1a6cd1..d937c39b3a0b 100644
--- a/drivers/net/wireless/ath/ath9k/mac.c
+++ b/drivers/net/wireless/ath/ath9k/mac.c
@@ -805,21 +805,12 @@ void ath9k_hw_disable_interrupts(struct ath_hw *ah)
805} 805}
806EXPORT_SYMBOL(ath9k_hw_disable_interrupts); 806EXPORT_SYMBOL(ath9k_hw_disable_interrupts);
807 807
808void ath9k_hw_enable_interrupts(struct ath_hw *ah) 808static void __ath9k_hw_enable_interrupts(struct ath_hw *ah)
809{ 809{
810 struct ath_common *common = ath9k_hw_common(ah); 810 struct ath_common *common = ath9k_hw_common(ah);
811 u32 sync_default = AR_INTR_SYNC_DEFAULT; 811 u32 sync_default = AR_INTR_SYNC_DEFAULT;
812 u32 async_mask; 812 u32 async_mask;
813 813
814 if (!(ah->imask & ATH9K_INT_GLOBAL))
815 return;
816
817 if (!atomic_inc_and_test(&ah->intr_ref_cnt)) {
818 ath_dbg(common, INTERRUPT, "Do not enable IER ref count %d\n",
819 atomic_read(&ah->intr_ref_cnt));
820 return;
821 }
822
823 if (AR_SREV_9340(ah) || AR_SREV_9550(ah) || AR_SREV_9531(ah) || 814 if (AR_SREV_9340(ah) || AR_SREV_9550(ah) || AR_SREV_9531(ah) ||
824 AR_SREV_9561(ah)) 815 AR_SREV_9561(ah))
825 sync_default &= ~AR_INTR_SYNC_HOST1_FATAL; 816 sync_default &= ~AR_INTR_SYNC_HOST1_FATAL;
@@ -841,6 +832,39 @@ void ath9k_hw_enable_interrupts(struct ath_hw *ah)
841 ath_dbg(common, INTERRUPT, "AR_IMR 0x%x IER 0x%x\n", 832 ath_dbg(common, INTERRUPT, "AR_IMR 0x%x IER 0x%x\n",
842 REG_READ(ah, AR_IMR), REG_READ(ah, AR_IER)); 833 REG_READ(ah, AR_IMR), REG_READ(ah, AR_IER));
843} 834}
835
836void ath9k_hw_resume_interrupts(struct ath_hw *ah)
837{
838 struct ath_common *common = ath9k_hw_common(ah);
839
840 if (!(ah->imask & ATH9K_INT_GLOBAL))
841 return;
842
843 if (atomic_read(&ah->intr_ref_cnt) != 0) {
844 ath_dbg(common, INTERRUPT, "Do not enable IER ref count %d\n",
845 atomic_read(&ah->intr_ref_cnt));
846 return;
847 }
848
849 __ath9k_hw_enable_interrupts(ah);
850}
851EXPORT_SYMBOL(ath9k_hw_resume_interrupts);
852
853void ath9k_hw_enable_interrupts(struct ath_hw *ah)
854{
855 struct ath_common *common = ath9k_hw_common(ah);
856
857 if (!(ah->imask & ATH9K_INT_GLOBAL))
858 return;
859
860 if (!atomic_inc_and_test(&ah->intr_ref_cnt)) {
861 ath_dbg(common, INTERRUPT, "Do not enable IER ref count %d\n",
862 atomic_read(&ah->intr_ref_cnt));
863 return;
864 }
865
866 __ath9k_hw_enable_interrupts(ah);
867}
844EXPORT_SYMBOL(ath9k_hw_enable_interrupts); 868EXPORT_SYMBOL(ath9k_hw_enable_interrupts);
845 869
846void ath9k_hw_set_interrupts(struct ath_hw *ah) 870void ath9k_hw_set_interrupts(struct ath_hw *ah)
diff --git a/drivers/net/wireless/ath/ath9k/mac.h b/drivers/net/wireless/ath/ath9k/mac.h
index 3bab01435a86..770fc11b41d1 100644
--- a/drivers/net/wireless/ath/ath9k/mac.h
+++ b/drivers/net/wireless/ath/ath9k/mac.h
@@ -744,6 +744,7 @@ void ath9k_hw_set_interrupts(struct ath_hw *ah);
744void ath9k_hw_enable_interrupts(struct ath_hw *ah); 744void ath9k_hw_enable_interrupts(struct ath_hw *ah);
745void ath9k_hw_disable_interrupts(struct ath_hw *ah); 745void ath9k_hw_disable_interrupts(struct ath_hw *ah);
746void ath9k_hw_kill_interrupts(struct ath_hw *ah); 746void ath9k_hw_kill_interrupts(struct ath_hw *ah);
747void ath9k_hw_resume_interrupts(struct ath_hw *ah);
747 748
748void ar9002_hw_attach_mac_ops(struct ath_hw *ah); 749void ar9002_hw_attach_mac_ops(struct ath_hw *ah);
749 750
diff --git a/drivers/net/wireless/ath/ath9k/main.c b/drivers/net/wireless/ath/ath9k/main.c
index e9f32b52fc8c..b868f02ced89 100644
--- a/drivers/net/wireless/ath/ath9k/main.c
+++ b/drivers/net/wireless/ath/ath9k/main.c
@@ -373,21 +373,20 @@ void ath9k_tasklet(unsigned long data)
373 struct ath_common *common = ath9k_hw_common(ah); 373 struct ath_common *common = ath9k_hw_common(ah);
374 enum ath_reset_type type; 374 enum ath_reset_type type;
375 unsigned long flags; 375 unsigned long flags;
376 u32 status = sc->intrstatus; 376 u32 status;
377 u32 rxmask; 377 u32 rxmask;
378 378
379 spin_lock_irqsave(&sc->intr_lock, flags);
380 status = sc->intrstatus;
381 sc->intrstatus = 0;
382 spin_unlock_irqrestore(&sc->intr_lock, flags);
383
379 ath9k_ps_wakeup(sc); 384 ath9k_ps_wakeup(sc);
380 spin_lock(&sc->sc_pcu_lock); 385 spin_lock(&sc->sc_pcu_lock);
381 386
382 if (status & ATH9K_INT_FATAL) { 387 if (status & ATH9K_INT_FATAL) {
383 type = RESET_TYPE_FATAL_INT; 388 type = RESET_TYPE_FATAL_INT;
384 ath9k_queue_reset(sc, type); 389 ath9k_queue_reset(sc, type);
385
386 /*
387 * Increment the ref. counter here so that
388 * interrupts are enabled in the reset routine.
389 */
390 atomic_inc(&ah->intr_ref_cnt);
391 ath_dbg(common, RESET, "FATAL: Skipping interrupts\n"); 390 ath_dbg(common, RESET, "FATAL: Skipping interrupts\n");
392 goto out; 391 goto out;
393 } 392 }
@@ -403,11 +402,6 @@ void ath9k_tasklet(unsigned long data)
403 type = RESET_TYPE_BB_WATCHDOG; 402 type = RESET_TYPE_BB_WATCHDOG;
404 ath9k_queue_reset(sc, type); 403 ath9k_queue_reset(sc, type);
405 404
406 /*
407 * Increment the ref. counter here so that
408 * interrupts are enabled in the reset routine.
409 */
410 atomic_inc(&ah->intr_ref_cnt);
411 ath_dbg(common, RESET, 405 ath_dbg(common, RESET,
412 "BB_WATCHDOG: Skipping interrupts\n"); 406 "BB_WATCHDOG: Skipping interrupts\n");
413 goto out; 407 goto out;
@@ -420,7 +414,6 @@ void ath9k_tasklet(unsigned long data)
420 if ((sc->gtt_cnt >= MAX_GTT_CNT) && !ath9k_hw_check_alive(ah)) { 414 if ((sc->gtt_cnt >= MAX_GTT_CNT) && !ath9k_hw_check_alive(ah)) {
421 type = RESET_TYPE_TX_GTT; 415 type = RESET_TYPE_TX_GTT;
422 ath9k_queue_reset(sc, type); 416 ath9k_queue_reset(sc, type);
423 atomic_inc(&ah->intr_ref_cnt);
424 ath_dbg(common, RESET, 417 ath_dbg(common, RESET,
425 "GTT: Skipping interrupts\n"); 418 "GTT: Skipping interrupts\n");
426 goto out; 419 goto out;
@@ -477,7 +470,7 @@ void ath9k_tasklet(unsigned long data)
477 ath9k_btcoex_handle_interrupt(sc, status); 470 ath9k_btcoex_handle_interrupt(sc, status);
478 471
479 /* re-enable hardware interrupt */ 472 /* re-enable hardware interrupt */
480 ath9k_hw_enable_interrupts(ah); 473 ath9k_hw_resume_interrupts(ah);
481out: 474out:
482 spin_unlock(&sc->sc_pcu_lock); 475 spin_unlock(&sc->sc_pcu_lock);
483 ath9k_ps_restore(sc); 476 ath9k_ps_restore(sc);
@@ -541,7 +534,9 @@ irqreturn_t ath_isr(int irq, void *dev)
541 return IRQ_NONE; 534 return IRQ_NONE;
542 535
543 /* Cache the status */ 536 /* Cache the status */
544 sc->intrstatus = status; 537 spin_lock(&sc->intr_lock);
538 sc->intrstatus |= status;
539 spin_unlock(&sc->intr_lock);
545 540
546 if (status & SCHED_INTR) 541 if (status & SCHED_INTR)
547 sched = true; 542 sched = true;
@@ -587,7 +582,7 @@ chip_reset:
587 582
588 if (sched) { 583 if (sched) {
589 /* turn off every interrupt */ 584 /* turn off every interrupt */
590 ath9k_hw_disable_interrupts(ah); 585 ath9k_hw_kill_interrupts(ah);
591 tasklet_schedule(&sc->intr_tq); 586 tasklet_schedule(&sc->intr_tq);
592 } 587 }
593 588