Fresh pull from upstream 17.01 branch
[librecmc/librecmc.git] / package / kernel / mac80211 / patches / 316-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 @@ -374,21 +374,20 @@ 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         if (status & ATH9K_INT_FATAL) {
138                 type = RESET_TYPE_FATAL_INT;
139                 ath9k_queue_reset(sc, type);
140 -
141 -               /*
142 -                * Increment the ref. counter here so that
143 -                * interrupts are enabled in the reset routine.
144 -                */
145 -               atomic_inc(&ah->intr_ref_cnt);
146                 ath_dbg(common, RESET, "FATAL: Skipping interrupts\n");
147                 goto out;
148         }
149 @@ -404,11 +403,6 @@ void ath9k_tasklet(unsigned long data)
150                         type = RESET_TYPE_BB_WATCHDOG;
151                         ath9k_queue_reset(sc, type);
152  
153 -                       /*
154 -                        * Increment the ref. counter here so that
155 -                        * interrupts are enabled in the reset routine.
156 -                        */
157 -                       atomic_inc(&ah->intr_ref_cnt);
158                         ath_dbg(common, RESET,
159                                 "BB_WATCHDOG: Skipping interrupts\n");
160                         goto out;
161 @@ -421,7 +415,6 @@ void ath9k_tasklet(unsigned long data)
162                 if ((sc->gtt_cnt >= MAX_GTT_CNT) && !ath9k_hw_check_alive(ah)) {
163                         type = RESET_TYPE_TX_GTT;
164                         ath9k_queue_reset(sc, type);
165 -                       atomic_inc(&ah->intr_ref_cnt);
166                         ath_dbg(common, RESET,
167                                 "GTT: Skipping interrupts\n");
168                         goto out;
169 @@ -478,7 +471,7 @@ void ath9k_tasklet(unsigned long data)
170         ath9k_btcoex_handle_interrupt(sc, status);
171  
172         /* re-enable hardware interrupt */
173 -       ath9k_hw_enable_interrupts(ah);
174 +       ath9k_hw_resume_interrupts(ah);
175  out:
176         spin_unlock(&sc->sc_pcu_lock);
177         ath9k_ps_restore(sc);
178 @@ -542,7 +535,9 @@ irqreturn_t ath_isr(int irq, void *dev)
179                 return IRQ_NONE;
180  
181         /* Cache the status */
182 -       sc->intrstatus = status;
183 +       spin_lock(&sc->intr_lock);
184 +       sc->intrstatus |= status;
185 +       spin_unlock(&sc->intr_lock);
186  
187         if (status & SCHED_INTR)
188                 sched = true;
189 @@ -588,7 +583,7 @@ chip_reset:
190  
191         if (sched) {
192                 /* turn off every interrupt */
193 -               ath9k_hw_disable_interrupts(ah);
194 +               ath9k_hw_kill_interrupts(ah);
195                 tasklet_schedule(&sc->intr_tq);
196         }
197