Skip to content

Commit 923bf98

Browse files
committed
wifi: iwlwifi: mvm: protect TXQ list manipulation
Some recent upstream debugging uncovered the fact that in iwlwifi, the TXQ list manipulation is racy. Introduce a new state bit for when the TXQ is completely ready and can be used without locking, and if that's not set yet acquire the lock to check everything correctly. Reviewed-by: Benjamin Berg <[email protected]> Tested-by: Jose Ignacio Tornos Martinez <[email protected]> Signed-off-by: Johannes Berg <[email protected]>
1 parent b58e3d4 commit 923bf98

File tree

4 files changed

+39
-34
lines changed

4 files changed

+39
-34
lines changed

drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c

Lines changed: 14 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -760,42 +760,25 @@ static void iwl_mvm_mac_wake_tx_queue(struct ieee80211_hw *hw,
760760
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
761761
struct iwl_mvm_txq *mvmtxq = iwl_mvm_txq_from_mac80211(txq);
762762

763-
/*
764-
* Please note that racing is handled very carefully here:
765-
* mvmtxq->txq_id is updated during allocation, and mvmtxq->list is
766-
* deleted afterwards.
767-
* This means that if:
768-
* mvmtxq->txq_id != INVALID_QUEUE && list_empty(&mvmtxq->list):
769-
* queue is allocated and we can TX.
770-
* mvmtxq->txq_id != INVALID_QUEUE && !list_empty(&mvmtxq->list):
771-
* a race, should defer the frame.
772-
* mvmtxq->txq_id == INVALID_QUEUE && list_empty(&mvmtxq->list):
773-
* need to allocate the queue and defer the frame.
774-
* mvmtxq->txq_id == INVALID_QUEUE && !list_empty(&mvmtxq->list):
775-
* queue is already scheduled for allocation, no need to allocate,
776-
* should defer the frame.
777-
*/
778-
779-
/* If the queue is allocated TX and return. */
780-
if (!txq->sta || mvmtxq->txq_id != IWL_MVM_INVALID_QUEUE) {
781-
/*
782-
* Check that list is empty to avoid a race where txq_id is
783-
* already updated, but the queue allocation work wasn't
784-
* finished
785-
*/
786-
if (unlikely(txq->sta && !list_empty(&mvmtxq->list)))
787-
return;
788-
763+
if (likely(test_bit(IWL_MVM_TXQ_STATE_READY, &mvmtxq->state)) ||
764+
!txq->sta) {
789765
iwl_mvm_mac_itxq_xmit(hw, txq);
790766
return;
791767
}
792768

793-
/* The list is being deleted only after the queue is fully allocated. */
794-
if (!list_empty(&mvmtxq->list))
795-
return;
769+
/* iwl_mvm_mac_itxq_xmit() will later be called by the worker
770+
* to handle any packets we leave on the txq now
771+
*/
796772

797-
list_add_tail(&mvmtxq->list, &mvm->add_stream_txqs);
798-
schedule_work(&mvm->add_stream_wk);
773+
spin_lock_bh(&mvm->add_stream_lock);
774+
/* The list is being deleted only after the queue is fully allocated. */
775+
if (list_empty(&mvmtxq->list) &&
776+
/* recheck under lock */
777+
!test_bit(IWL_MVM_TXQ_STATE_READY, &mvmtxq->state)) {
778+
list_add_tail(&mvmtxq->list, &mvm->add_stream_txqs);
779+
schedule_work(&mvm->add_stream_wk);
780+
}
781+
spin_unlock_bh(&mvm->add_stream_lock);
799782
}
800783

801784
#define CHECK_BA_TRIGGER(_mvm, _trig, _tid_bm, _tid, _fmt...) \

drivers/net/wireless/intel/iwlwifi/mvm/mvm.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,7 @@ struct iwl_mvm_txq {
731731
atomic_t tx_request;
732732
#define IWL_MVM_TXQ_STATE_STOP_FULL 0
733733
#define IWL_MVM_TXQ_STATE_STOP_REDIRECT 1
734+
#define IWL_MVM_TXQ_STATE_READY 2
734735
unsigned long state;
735736
};
736737

@@ -829,6 +830,7 @@ struct iwl_mvm {
829830
struct iwl_mvm_tvqm_txq_info tvqm_info[IWL_MAX_TVQM_QUEUES];
830831
};
831832
struct work_struct add_stream_wk; /* To add streams to queues */
833+
spinlock_t add_stream_lock;
832834

833835
const char *nvm_file_name;
834836
struct iwl_nvm_data *nvm_data;

drivers/net/wireless/intel/iwlwifi/mvm/ops.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,7 @@ iwl_op_mode_mvm_start(struct iwl_trans *trans, const struct iwl_cfg *cfg,
11951195
INIT_DELAYED_WORK(&mvm->scan_timeout_dwork, iwl_mvm_scan_timeout_wk);
11961196
INIT_WORK(&mvm->add_stream_wk, iwl_mvm_add_new_dqa_stream_wk);
11971197
INIT_LIST_HEAD(&mvm->add_stream_txqs);
1198+
spin_lock_init(&mvm->add_stream_lock);
11981199

11991200
init_waitqueue_head(&mvm->rx_sync_waitq);
12001201

drivers/net/wireless/intel/iwlwifi/mvm/sta.c

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -384,8 +384,11 @@ static int iwl_mvm_disable_txq(struct iwl_mvm *mvm, struct ieee80211_sta *sta,
384384
struct iwl_mvm_txq *mvmtxq =
385385
iwl_mvm_txq_from_tid(sta, tid);
386386

387-
mvmtxq->txq_id = IWL_MVM_INVALID_QUEUE;
387+
spin_lock_bh(&mvm->add_stream_lock);
388388
list_del_init(&mvmtxq->list);
389+
clear_bit(IWL_MVM_TXQ_STATE_READY, &mvmtxq->state);
390+
mvmtxq->txq_id = IWL_MVM_INVALID_QUEUE;
391+
spin_unlock_bh(&mvm->add_stream_lock);
389392
}
390393

391394
/* Regardless if this is a reserved TXQ for a STA - mark it as false */
@@ -479,8 +482,11 @@ static int iwl_mvm_remove_sta_queue_marking(struct iwl_mvm *mvm, int queue)
479482
disable_agg_tids |= BIT(tid);
480483
mvmsta->tid_data[tid].txq_id = IWL_MVM_INVALID_QUEUE;
481484

482-
mvmtxq->txq_id = IWL_MVM_INVALID_QUEUE;
485+
spin_lock_bh(&mvm->add_stream_lock);
483486
list_del_init(&mvmtxq->list);
487+
clear_bit(IWL_MVM_TXQ_STATE_READY, &mvmtxq->state);
488+
mvmtxq->txq_id = IWL_MVM_INVALID_QUEUE;
489+
spin_unlock_bh(&mvm->add_stream_lock);
484490
}
485491

486492
mvmsta->tfd_queue_msk &= ~BIT(queue); /* Don't use this queue anymore */
@@ -1444,12 +1450,22 @@ void iwl_mvm_add_new_dqa_stream_wk(struct work_struct *wk)
14441450
* a queue in the function itself.
14451451
*/
14461452
if (iwl_mvm_sta_alloc_queue(mvm, txq->sta, txq->ac, tid)) {
1453+
spin_lock_bh(&mvm->add_stream_lock);
14471454
list_del_init(&mvmtxq->list);
1455+
spin_unlock_bh(&mvm->add_stream_lock);
14481456
continue;
14491457
}
14501458

1451-
list_del_init(&mvmtxq->list);
1459+
/* now we're ready, any remaining races/concurrency will be
1460+
* handled in iwl_mvm_mac_itxq_xmit()
1461+
*/
1462+
set_bit(IWL_MVM_TXQ_STATE_READY, &mvmtxq->state);
1463+
14521464
local_bh_disable();
1465+
spin_lock(&mvm->add_stream_lock);
1466+
list_del_init(&mvmtxq->list);
1467+
spin_unlock(&mvm->add_stream_lock);
1468+
14531469
iwl_mvm_mac_itxq_xmit(mvm->hw, txq);
14541470
local_bh_enable();
14551471
}
@@ -1864,8 +1880,11 @@ static void iwl_mvm_disable_sta_queues(struct iwl_mvm *mvm,
18641880
struct iwl_mvm_txq *mvmtxq =
18651881
iwl_mvm_txq_from_mac80211(sta->txq[i]);
18661882

1883+
spin_lock_bh(&mvm->add_stream_lock);
18671884
mvmtxq->txq_id = IWL_MVM_INVALID_QUEUE;
18681885
list_del_init(&mvmtxq->list);
1886+
clear_bit(IWL_MVM_TXQ_STATE_READY, &mvmtxq->state);
1887+
spin_unlock_bh(&mvm->add_stream_lock);
18691888
}
18701889
}
18711890

0 commit comments

Comments
 (0)