Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ath9k: prevent aggregation session deadlocks

Waiting for all subframes of an existing aggregation session to drain
before allowing mac80211 to start a new one is fragile and deadlocks
caused by this behavior have been observed.

Since mac80211 has proper synchronization for aggregation session
start/stop handling, a better approach to session handling is to simply
allow mac80211 to start a new session at any time. This requires
changing the code to discard any packets outside of the BlockAck window
in the A-MPDU software retry code.

This patch implements the above and also simplifies the code.

Signed-off-by: Felix Fietkau <nbd@openwrt.org>
Signed-off-by: John W. Linville <linville@tuxdriver.com>

authored by

Felix Fietkau and committed by
John W. Linville
08c96abd 323a98db

+46 -114
+4 -10
drivers/net/wireless/ath/ath9k/ath9k.h
··· 251 251 int tidno; 252 252 int baw_head; /* first un-acked tx buffer */ 253 253 int baw_tail; /* next unused tx buffer slot */ 254 - int sched; 255 - int paused; 256 - u8 state; 257 - bool stop_cb; 254 + bool sched; 255 + bool paused; 256 + bool active; 258 257 }; 259 258 260 259 struct ath_node { ··· 273 274 struct dentry *node_stat; 274 275 #endif 275 276 }; 276 - 277 - #define AGGR_CLEANUP BIT(1) 278 - #define AGGR_ADDBA_COMPLETE BIT(2) 279 - #define AGGR_ADDBA_PROGRESS BIT(3) 280 277 281 278 struct ath_tx_control { 282 279 struct ath_txq *txq; ··· 347 352 void ath_tx_edma_tasklet(struct ath_softc *sc); 348 353 int ath_tx_aggr_start(struct ath_softc *sc, struct ieee80211_sta *sta, 349 354 u16 tid, u16 *ssn); 350 - bool ath_tx_aggr_stop(struct ath_softc *sc, struct ieee80211_sta *sta, u16 tid, 351 - bool flush); 355 + void ath_tx_aggr_stop(struct ath_softc *sc, struct ieee80211_sta *sta, u16 tid); 352 356 void ath_tx_aggr_resume(struct ath_softc *sc, struct ieee80211_sta *sta, u16 tid); 353 357 354 358 void ath_tx_aggr_wakeup(struct ath_softc *sc, struct ath_node *an);
+2 -1
drivers/net/wireless/ath/ath9k/main.c
··· 1709 1709 flush = true; 1710 1710 case IEEE80211_AMPDU_TX_STOP_CONT: 1711 1711 ath9k_ps_wakeup(sc); 1712 - if (ath_tx_aggr_stop(sc, sta, tid, flush)) 1712 + ath_tx_aggr_stop(sc, sta, tid); 1713 + if (!flush) 1713 1714 ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); 1714 1715 ath9k_ps_restore(sc); 1715 1716 break;
+1 -4
drivers/net/wireless/ath/ath9k/rc.c
··· 1227 1227 return false; 1228 1228 1229 1229 txtid = ATH_AN_2_TID(an, tidno); 1230 - 1231 - if (!(txtid->state & (AGGR_ADDBA_COMPLETE | AGGR_ADDBA_PROGRESS))) 1232 - return true; 1233 - return false; 1230 + return !txtid->active; 1234 1231 } 1235 1232 1236 1233
+39 -99
drivers/net/wireless/ath/ath9k/xmit.c
··· 125 125 list_add_tail(&ac->list, &txq->axq_acq); 126 126 } 127 127 128 - static void ath_tx_resume_tid(struct ath_softc *sc, struct ath_atx_tid *tid) 129 - { 130 - struct ath_txq *txq = tid->ac->txq; 131 - 132 - WARN_ON(!tid->paused); 133 - 134 - ath_txq_lock(sc, txq); 135 - tid->paused = false; 136 - 137 - if (skb_queue_empty(&tid->buf_q)) 138 - goto unlock; 139 - 140 - ath_tx_queue_tid(txq, tid); 141 - ath_txq_schedule(sc, txq); 142 - unlock: 143 - ath_txq_unlock_complete(sc, txq); 144 - } 145 - 146 128 static struct ath_frame_info *get_frame_info(struct sk_buff *skb) 147 129 { 148 130 struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); ··· 146 164 ARRAY_SIZE(bf->rates)); 147 165 } 148 166 149 - static void ath_tx_clear_tid(struct ath_softc *sc, struct ath_atx_tid *tid) 150 - { 151 - tid->state &= ~AGGR_ADDBA_COMPLETE; 152 - tid->state &= ~AGGR_CLEANUP; 153 - if (!tid->stop_cb) 154 - return; 155 - 156 - ieee80211_start_tx_ba_cb_irqsafe(tid->an->vif, tid->an->sta->addr, 157 - tid->tidno); 158 - tid->stop_cb = false; 159 - } 160 - 161 - static void ath_tx_flush_tid(struct ath_softc *sc, struct ath_atx_tid *tid, 162 - bool flush_packets) 167 + static void ath_tx_flush_tid(struct ath_softc *sc, struct ath_atx_tid *tid) 163 168 { 164 169 struct ath_txq *txq = tid->ac->txq; 165 170 struct sk_buff *skb; ··· 163 194 while ((skb = __skb_dequeue(&tid->buf_q))) { 164 195 fi = get_frame_info(skb); 165 196 bf = fi->bf; 166 - if (!bf && !flush_packets) 167 - bf = ath_tx_setup_buffer(sc, txq, tid, skb); 168 197 169 198 if (!bf) { 170 - ieee80211_free_txskb(sc->hw, skb); 171 - continue; 199 + bf = ath_tx_setup_buffer(sc, txq, tid, skb); 200 + if (!bf) { 201 + ieee80211_free_txskb(sc->hw, skb); 202 + continue; 203 + } 172 204 } 173 205 174 - if (fi->retries || flush_packets) { 206 + if (fi->retries) { 175 207 list_add_tail(&bf->list, &bf_head); 176 208 ath_tx_update_baw(sc, tid, bf->bf_state.seqno); 177 209 ath_tx_complete_buf(sc, bf, txq, &bf_head, &ts, 0); ··· 183 213 } 184 214 } 185 215 186 - if (tid->baw_head == tid->baw_tail) 187 - ath_tx_clear_tid(sc, tid); 188 - 189 - if (sendbar && !flush_packets) { 216 + if (sendbar) { 190 217 ath_txq_unlock(sc, txq); 191 218 ath_send_bar(tid, tid->seq_start); 192 219 ath_txq_lock(sc, txq); ··· 466 499 tx_info = IEEE80211_SKB_CB(skb); 467 500 fi = get_frame_info(skb); 468 501 469 - if (ATH_BA_ISSET(ba, ATH_BA_INDEX(seq_st, seqno))) { 502 + if (!BAW_WITHIN(tid->seq_start, tid->baw_size, seqno)) { 503 + /* 504 + * Outside of the current BlockAck window, 505 + * maybe part of a previous session 506 + */ 507 + txfail = 1; 508 + } else if (ATH_BA_ISSET(ba, ATH_BA_INDEX(seq_st, seqno))) { 470 509 /* transmit completion, subframe is 471 510 * acked by block ack */ 472 511 acked_cnt++; 473 512 } else if (!isaggr && txok) { 474 513 /* transmit completion */ 475 514 acked_cnt++; 476 - } else if (tid->state & AGGR_CLEANUP) { 477 - /* 478 - * cleanup in progress, just fail 479 - * the un-acked sub-frames 480 - */ 481 - txfail = 1; 482 515 } else if (flush) { 483 516 txpending = 1; 484 517 } else if (fi->retries < ATH_MAX_SW_RETRIES) { ··· 502 535 if (bf_next != NULL || !bf_last->bf_stale) 503 536 list_move_tail(&bf->list, &bf_head); 504 537 505 - if (!txpending || (tid->state & AGGR_CLEANUP)) { 538 + if (!txpending) { 506 539 /* 507 540 * complete the acked-ones/xretried ones; update 508 541 * block-ack window ··· 575 608 ath_send_bar(tid, ATH_BA_INDEX2SEQ(seq_first, bar_index + 1)); 576 609 ath_txq_lock(sc, txq); 577 610 } 578 - 579 - if (tid->state & AGGR_CLEANUP) 580 - ath_tx_flush_tid(sc, tid, false); 581 611 582 612 rcu_read_unlock(); 583 613 ··· 1208 1244 an = (struct ath_node *)sta->drv_priv; 1209 1245 txtid = ATH_AN_2_TID(an, tid); 1210 1246 1211 - if (txtid->state & (AGGR_CLEANUP | AGGR_ADDBA_COMPLETE)) 1212 - return -EAGAIN; 1213 - 1214 1247 /* update ampdu factor/density, they may have changed. This may happen 1215 1248 * in HT IBSS when a beacon with HT-info is received after the station 1216 1249 * has already been added. ··· 1219 1258 an->mpdudensity = density; 1220 1259 } 1221 1260 1222 - txtid->state |= AGGR_ADDBA_PROGRESS; 1261 + txtid->active = true; 1223 1262 txtid->paused = true; 1224 1263 *ssn = txtid->seq_start = txtid->seq_next; 1225 1264 txtid->bar_index = -1; ··· 1230 1269 return 0; 1231 1270 } 1232 1271 1233 - bool ath_tx_aggr_stop(struct ath_softc *sc, struct ieee80211_sta *sta, u16 tid, 1234 - bool flush) 1272 + void ath_tx_aggr_stop(struct ath_softc *sc, struct ieee80211_sta *sta, u16 tid) 1235 1273 { 1236 1274 struct ath_node *an = (struct ath_node *)sta->drv_priv; 1237 1275 struct ath_atx_tid *txtid = ATH_AN_2_TID(an, tid); 1238 1276 struct ath_txq *txq = txtid->ac->txq; 1239 - bool ret = !flush; 1240 - 1241 - if (flush) 1242 - txtid->stop_cb = false; 1243 - 1244 - if (txtid->state & AGGR_CLEANUP) 1245 - return false; 1246 - 1247 - if (!(txtid->state & AGGR_ADDBA_COMPLETE)) { 1248 - txtid->state &= ~AGGR_ADDBA_PROGRESS; 1249 - return ret; 1250 - } 1251 1277 1252 1278 ath_txq_lock(sc, txq); 1279 + txtid->active = false; 1253 1280 txtid->paused = true; 1254 - 1255 - /* 1256 - * If frames are still being transmitted for this TID, they will be 1257 - * cleaned up during tx completion. To prevent race conditions, this 1258 - * TID can only be reused after all in-progress subframes have been 1259 - * completed. 1260 - */ 1261 - if (txtid->baw_head != txtid->baw_tail) { 1262 - txtid->state |= AGGR_CLEANUP; 1263 - ret = false; 1264 - txtid->stop_cb = !flush; 1265 - } else { 1266 - txtid->state &= ~AGGR_ADDBA_COMPLETE; 1267 - } 1268 - 1269 - ath_tx_flush_tid(sc, txtid, flush); 1281 + ath_tx_flush_tid(sc, txtid); 1270 1282 ath_txq_unlock_complete(sc, txq); 1271 - return ret; 1272 1283 } 1273 1284 1274 1285 void ath_tx_aggr_sleep(struct ieee80211_sta *sta, struct ath_softc *sc, ··· 1304 1371 } 1305 1372 } 1306 1373 1307 - void ath_tx_aggr_resume(struct ath_softc *sc, struct ieee80211_sta *sta, u16 tid) 1374 + void ath_tx_aggr_resume(struct ath_softc *sc, struct ieee80211_sta *sta, 1375 + u16 tidno) 1308 1376 { 1309 - struct ath_atx_tid *txtid; 1377 + struct ath_atx_tid *tid; 1310 1378 struct ath_node *an; 1379 + struct ath_txq *txq; 1311 1380 1312 1381 an = (struct ath_node *)sta->drv_priv; 1382 + tid = ATH_AN_2_TID(an, tidno); 1383 + txq = tid->ac->txq; 1313 1384 1314 - txtid = ATH_AN_2_TID(an, tid); 1315 - txtid->baw_size = IEEE80211_MIN_AMPDU_BUF << sta->ht_cap.ampdu_factor; 1316 - txtid->state |= AGGR_ADDBA_COMPLETE; 1317 - txtid->state &= ~AGGR_ADDBA_PROGRESS; 1318 - ath_tx_resume_tid(sc, txtid); 1385 + ath_txq_lock(sc, txq); 1386 + 1387 + tid->baw_size = IEEE80211_MIN_AMPDU_BUF << sta->ht_cap.ampdu_factor; 1388 + tid->paused = false; 1389 + 1390 + if (!skb_queue_empty(&tid->buf_q)) { 1391 + ath_tx_queue_tid(txq, tid); 1392 + ath_txq_schedule(sc, txq); 1393 + } 1394 + 1395 + ath_txq_unlock_complete(sc, txq); 1319 1396 } 1320 1397 1321 1398 /********************/ ··· 2374 2431 tid->baw_head = tid->baw_tail = 0; 2375 2432 tid->sched = false; 2376 2433 tid->paused = false; 2377 - tid->state &= ~AGGR_CLEANUP; 2434 + tid->active = false; 2378 2435 __skb_queue_head_init(&tid->buf_q); 2379 2436 acno = TID_TO_WME_AC(tidno); 2380 2437 tid->ac = &an->ac[acno]; 2381 - tid->state &= ~AGGR_ADDBA_COMPLETE; 2382 - tid->state &= ~AGGR_ADDBA_PROGRESS; 2383 - tid->stop_cb = false; 2384 2438 } 2385 2439 2386 2440 for (acno = 0, ac = &an->ac[acno]; ··· 2414 2474 } 2415 2475 2416 2476 ath_tid_drain(sc, txq, tid); 2417 - ath_tx_clear_tid(sc, tid); 2477 + tid->active = false; 2418 2478 2419 2479 ath_txq_unlock(sc, txq); 2420 2480 }