kernel: add missing config symbols for 4.9
[openwrt/openwrt.git] / package / kernel / mac80211 / patches / 356-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