ath9k: add stability fixes for long standing hang issues (FS#13, #34, #373, #383)
[lede.git] / package / kernel / mac80211 / patches / 357-ath9k-fix-race-condition-in-enabling-disabling-IRQs.patch
1 From: Felix Fietkau <nbd@nbd.name>
2 Date: Wed, 25 Jan 2017 15:10:37 +0100
3 Subject: [PATCH] ath9k: fix race condition in enabling/disabling IRQs
4
5 The code currently relies on refcounting to disable IRQs from within the
6 IRQ handler and re-enabling them again after the tasklet has run.
7
8 However, due to race conditions sometimes the IRQ handler might be
9 called twice, or the tasklet may not run at all (if interrupted in the
10 middle of a reset).
11
12 This can cause nasty imbalances in the irq-disable refcount which will
13 get the driver permanently stuck until the entire radio has been stopped
14 and started again (ath_reset will not recover from this).
15
16 Instead of using this fragile logic, change the code to ensure that
17 running the irq handler during tasklet processing is safe, and leave the
18 refcount untouched.
19
20 Cc: stable@vger.kernel.org
21 Signed-off-by: Felix Fietkau <nbd@nbd.name>
22 ---
23
24 --- a/drivers/net/wireless/ath/ath9k/ath9k.h
25 +++ b/drivers/net/wireless/ath/ath9k/ath9k.h
26 @@ -998,6 +998,7 @@ struct ath_softc {
27         struct survey_info *cur_survey;
28         struct survey_info survey[ATH9K_NUM_CHANNELS];
29  
30 +       spinlock_t intr_lock;
31         struct tasklet_struct intr_tq;
32         struct tasklet_struct bcon_tasklet;
33         struct ath_hw *sc_ah;
34 --- a/drivers/net/wireless/ath/ath9k/init.c
35 +++ b/drivers/net/wireless/ath/ath9k/init.c
36 @@ -669,6 +669,7 @@ static int ath9k_init_softc(u16 devid, s
37                 common->bt_ant_diversity = 1;
38  
39         spin_lock_init(&common->cc_lock);
40 +       spin_lock_init(&sc->intr_lock);
41         spin_lock_init(&sc->sc_serial_rw);
42         spin_lock_init(&sc->sc_pm_lock);
43         spin_lock_init(&sc->chan_lock);
44 --- a/drivers/net/wireless/ath/ath9k/mac.c
45 +++ b/drivers/net/wireless/ath/ath9k/mac.c
46 @@ -810,21 +810,12 @@ void ath9k_hw_disable_interrupts(struct
47  }
48  EXPORT_SYMBOL(ath9k_hw_disable_interrupts);
49  
50 -void ath9k_hw_enable_interrupts(struct ath_hw *ah)
51 +static void __ath9k_hw_enable_interrupts(struct ath_hw *ah)
52  {
53         struct ath_common *common = ath9k_hw_common(ah);
54         u32 sync_default = AR_INTR_SYNC_DEFAULT;
55         u32 async_mask;
56  
57 -       if (!(ah->imask & ATH9K_INT_GLOBAL))
58 -               return;
59 -
60 -       if (!atomic_inc_and_test(&ah->intr_ref_cnt)) {
61 -               ath_dbg(common, INTERRUPT, "Do not enable IER ref count %d\n",
62 -                       atomic_read(&ah->intr_ref_cnt));
63 -               return;
64 -       }
65 -
66         if (AR_SREV_9340(ah) || AR_SREV_9550(ah) || AR_SREV_9531(ah) ||
67             AR_SREV_9561(ah))
68                 sync_default &= ~AR_INTR_SYNC_HOST1_FATAL;
69 @@ -846,6 +837,39 @@ void ath9k_hw_enable_interrupts(struct a
70         ath_dbg(common, INTERRUPT, "AR_IMR 0x%x IER 0x%x\n",
71                 REG_READ(ah, AR_IMR), REG_READ(ah, AR_IER));
72  }
73 +
74 +void ath9k_hw_resume_interrupts(struct ath_hw *ah)
75 +{
76 +       struct ath_common *common = ath9k_hw_common(ah);
77 +
78 +       if (!(ah->imask & ATH9K_INT_GLOBAL))
79 +               return;
80 +
81 +       if (atomic_read(&ah->intr_ref_cnt) != 0) {
82 +               ath_dbg(common, INTERRUPT, "Do not enable IER ref count %d\n",
83 +                       atomic_read(&ah->intr_ref_cnt));
84 +               return;
85 +       }
86 +
87 +       __ath9k_hw_enable_interrupts(ah);
88 +}
89 +EXPORT_SYMBOL(ath9k_hw_resume_interrupts);
90 +
91 +void ath9k_hw_enable_interrupts(struct ath_hw *ah)
92 +{
93 +       struct ath_common *common = ath9k_hw_common(ah);
94 +
95 +       if (!(ah->imask & ATH9K_INT_GLOBAL))
96 +               return;
97 +
98 +       if (!atomic_inc_and_test(&ah->intr_ref_cnt)) {
99 +               ath_dbg(common, INTERRUPT, "Do not enable IER ref count %d\n",
100 +                       atomic_read(&ah->intr_ref_cnt));
101 +               return;
102 +       }
103 +
104 +       __ath9k_hw_enable_interrupts(ah);
105 +}
106  EXPORT_SYMBOL(ath9k_hw_enable_interrupts);
107  
108  void ath9k_hw_set_interrupts(struct ath_hw *ah)
109 --- a/drivers/net/wireless/ath/ath9k/mac.h
110 +++ b/drivers/net/wireless/ath/ath9k/mac.h
111 @@ -744,6 +744,7 @@ void ath9k_hw_set_interrupts(struct ath_
112  void ath9k_hw_enable_interrupts(struct ath_hw *ah);
113  void ath9k_hw_disable_interrupts(struct ath_hw *ah);
114  void ath9k_hw_kill_interrupts(struct ath_hw *ah);
115 +void ath9k_hw_resume_interrupts(struct ath_hw *ah);
116  
117  void ar9002_hw_attach_mac_ops(struct ath_hw *ah);
118  
119 --- a/drivers/net/wireless/ath/ath9k/main.c
120 +++ b/drivers/net/wireless/ath/ath9k/main.c
121 @@ -375,9 +375,14 @@ void ath9k_tasklet(unsigned long data)
122         struct ath_common *common = ath9k_hw_common(ah);
123         enum ath_reset_type type;
124         unsigned long flags;
125 -       u32 status = sc->intrstatus;
126 +       u32 status;
127         u32 rxmask;
128  
129 +       spin_lock_irqsave(&sc->intr_lock, flags);
130 +       status = sc->intrstatus;
131 +       sc->intrstatus = 0;
132 +       spin_unlock_irqrestore(&sc->intr_lock, flags);
133 +
134         ath9k_ps_wakeup(sc);
135         spin_lock(&sc->sc_pcu_lock);
136  
137 @@ -480,7 +485,7 @@ void ath9k_tasklet(unsigned long data)
138         ath9k_btcoex_handle_interrupt(sc, status);
139  
140         /* re-enable hardware interrupt */
141 -       ath9k_hw_enable_interrupts(ah);
142 +       ath9k_hw_resume_interrupts(ah);
143  out:
144         spin_unlock(&sc->sc_pcu_lock);
145         ath9k_ps_restore(sc);
146 @@ -544,7 +549,9 @@ irqreturn_t ath_isr(int irq, void *dev)
147                 return IRQ_NONE;
148  
149         /* Cache the status */
150 -       sc->intrstatus = status;
151 +       spin_lock(&sc->intr_lock);
152 +       sc->intrstatus |= status;
153 +       spin_unlock(&sc->intr_lock);
154  
155         if (status & SCHED_INTR)
156                 sched = true;
157 @@ -590,7 +597,7 @@ chip_reset:
158  
159         if (sched) {
160                 /* turn off every interrupt */
161 -               ath9k_hw_disable_interrupts(ah);
162 +               ath9k_hw_kill_interrupts(ah);
163                 tasklet_schedule(&sc->intr_tq);
164         }
165