Jiri Slaby 896490
From: Johannes Berg <johannes.berg@intel.com>
Jiri Slaby 896490
Date: Fri, 17 Mar 2023 10:53:25 +0100
Jiri Slaby 896490
Subject: [PATCH] wifi: iwlwifi: mvm: protect TXQ list manipulation
Jiri Slaby 896490
References: bsc#1012628
Jiri Slaby 896490
Patch-mainline: 6.2.12
Jiri Slaby 896490
Git-commit: 923bf981eb6ecc027227716e30701bdcc1845fbf
Jiri Slaby 896490
Jiri Slaby 896490
[ Upstream commit 923bf981eb6ecc027227716e30701bdcc1845fbf ]
Jiri Slaby 896490
Jiri Slaby 896490
Some recent upstream debugging uncovered the fact that in
Jiri Slaby 896490
iwlwifi, the TXQ list manipulation is racy.
Jiri Slaby 896490
Jiri Slaby 896490
Introduce a new state bit for when the TXQ is completely
Jiri Slaby 896490
ready and can be used without locking, and if that's not
Jiri Slaby 896490
set yet acquire the lock to check everything correctly.
Jiri Slaby 896490
Jiri Slaby 896490
Reviewed-by: Benjamin Berg <benjamin.berg@intel.com>
Jiri Slaby 896490
Tested-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
Jiri Slaby 896490
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Jiri Slaby 896490
Signed-off-by: Sasha Levin <sashal@kernel.org>
Jiri Slaby 896490
Signed-off-by: Jiri Slaby <jslaby@suse.cz>
Jiri Slaby 896490
---
Jiri Slaby 896490
 .../net/wireless/intel/iwlwifi/mvm/mac80211.c | 45 ++++++-------------
Jiri Slaby 896490
 drivers/net/wireless/intel/iwlwifi/mvm/mvm.h  |  2 +
Jiri Slaby 896490
 drivers/net/wireless/intel/iwlwifi/mvm/ops.c  |  1 +
Jiri Slaby 896490
 drivers/net/wireless/intel/iwlwifi/mvm/sta.c  | 25 +++++++++--
Jiri Slaby 896490
 4 files changed, 39 insertions(+), 34 deletions(-)
Jiri Slaby 896490
Jiri Slaby 896490
diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c
Jiri Slaby 896490
index 5b497418..1d46a2b3 100644
Jiri Slaby 896490
--- a/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c
Jiri Slaby 896490
+++ b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c
Jiri Slaby 896490
@@ -760,42 +760,25 @@ static void iwl_mvm_mac_wake_tx_queue(struct ieee80211_hw *hw,
Jiri Slaby 896490
 	struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
Jiri Slaby 896490
 	struct iwl_mvm_txq *mvmtxq = iwl_mvm_txq_from_mac80211(txq);
Jiri Slaby 896490
 
Jiri Slaby 896490
-	/*
Jiri Slaby 896490
-	 * Please note that racing is handled very carefully here:
Jiri Slaby 896490
-	 * mvmtxq->txq_id is updated during allocation, and mvmtxq->list is
Jiri Slaby 896490
-	 * deleted afterwards.
Jiri Slaby 896490
-	 * This means that if:
Jiri Slaby 896490
-	 * mvmtxq->txq_id != INVALID_QUEUE && list_empty(&mvmtxq->list):
Jiri Slaby 896490
-	 *	queue is allocated and we can TX.
Jiri Slaby 896490
-	 * mvmtxq->txq_id != INVALID_QUEUE && !list_empty(&mvmtxq->list):
Jiri Slaby 896490
-	 *	a race, should defer the frame.
Jiri Slaby 896490
-	 * mvmtxq->txq_id == INVALID_QUEUE && list_empty(&mvmtxq->list):
Jiri Slaby 896490
-	 *	need to allocate the queue and defer the frame.
Jiri Slaby 896490
-	 * mvmtxq->txq_id == INVALID_QUEUE && !list_empty(&mvmtxq->list):
Jiri Slaby 896490
-	 *	queue is already scheduled for allocation, no need to allocate,
Jiri Slaby 896490
-	 *	should defer the frame.
Jiri Slaby 896490
-	 */
Jiri Slaby 896490
-
Jiri Slaby 896490
-	/* If the queue is allocated TX and return. */
Jiri Slaby 896490
-	if (!txq->sta || mvmtxq->txq_id != IWL_MVM_INVALID_QUEUE) {
Jiri Slaby 896490
-		/*
Jiri Slaby 896490
-		 * Check that list is empty to avoid a race where txq_id is
Jiri Slaby 896490
-		 * already updated, but the queue allocation work wasn't
Jiri Slaby 896490
-		 * finished
Jiri Slaby 896490
-		 */
Jiri Slaby 896490
-		if (unlikely(txq->sta && !list_empty(&mvmtxq->list)))
Jiri Slaby 896490
-			return;
Jiri Slaby 896490
-
Jiri Slaby 896490
+	if (likely(test_bit(IWL_MVM_TXQ_STATE_READY, &mvmtxq->state)) ||
Jiri Slaby 896490
+	    !txq->sta) {
Jiri Slaby 896490
 		iwl_mvm_mac_itxq_xmit(hw, txq);
Jiri Slaby 896490
 		return;
Jiri Slaby 896490
 	}
Jiri Slaby 896490
 
Jiri Slaby 896490
-	/* The list is being deleted only after the queue is fully allocated. */
Jiri Slaby 896490
-	if (!list_empty(&mvmtxq->list))
Jiri Slaby 896490
-		return;
Jiri Slaby 896490
+	/* iwl_mvm_mac_itxq_xmit() will later be called by the worker
Jiri Slaby 896490
+	 * to handle any packets we leave on the txq now
Jiri Slaby 896490
+	 */
Jiri Slaby 896490
 
Jiri Slaby 896490
-	list_add_tail(&mvmtxq->list, &mvm->add_stream_txqs);
Jiri Slaby 896490
-	schedule_work(&mvm->add_stream_wk);
Jiri Slaby 896490
+	spin_lock_bh(&mvm->add_stream_lock);
Jiri Slaby 896490
+	/* The list is being deleted only after the queue is fully allocated. */
Jiri Slaby 896490
+	if (list_empty(&mvmtxq->list) &&
Jiri Slaby 896490
+	    /* recheck under lock */
Jiri Slaby 896490
+	    !test_bit(IWL_MVM_TXQ_STATE_READY, &mvmtxq->state)) {
Jiri Slaby 896490
+		list_add_tail(&mvmtxq->list, &mvm->add_stream_txqs);
Jiri Slaby 896490
+		schedule_work(&mvm->add_stream_wk);
Jiri Slaby 896490
+	}
Jiri Slaby 896490
+	spin_unlock_bh(&mvm->add_stream_lock);
Jiri Slaby 896490
 }
Jiri Slaby 896490
 
Jiri Slaby 896490
 #define CHECK_BA_TRIGGER(_mvm, _trig, _tid_bm, _tid, _fmt...)		\
Jiri Slaby 896490
diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h
Jiri Slaby 896490
index 3146b3d0..157de77e 100644
Jiri Slaby 896490
--- a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h
Jiri Slaby 896490
+++ b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h
Jiri Slaby 896490
@@ -731,6 +731,7 @@ struct iwl_mvm_txq {
Jiri Slaby 896490
 	atomic_t tx_request;
Jiri Slaby 896490
 #define IWL_MVM_TXQ_STATE_STOP_FULL	0
Jiri Slaby 896490
 #define IWL_MVM_TXQ_STATE_STOP_REDIRECT	1
Jiri Slaby 896490
+#define IWL_MVM_TXQ_STATE_READY		2
Jiri Slaby 896490
 	unsigned long state;
Jiri Slaby 896490
 };
Jiri Slaby 896490
 
Jiri Slaby 896490
@@ -829,6 +830,7 @@ struct iwl_mvm {
Jiri Slaby 896490
 		struct iwl_mvm_tvqm_txq_info tvqm_info[IWL_MAX_TVQM_QUEUES];
Jiri Slaby 896490
 	};
Jiri Slaby 896490
 	struct work_struct add_stream_wk; /* To add streams to queues */
Jiri Slaby 896490
+	spinlock_t add_stream_lock;
Jiri Slaby 896490
 
Jiri Slaby 896490
 	const char *nvm_file_name;
Jiri Slaby 896490
 	struct iwl_nvm_data *nvm_data;
Jiri Slaby 896490
diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/ops.c b/drivers/net/wireless/intel/iwlwifi/mvm/ops.c
Jiri Slaby 896490
index f43e617f..c49a2a1e 100644
Jiri Slaby 896490
--- a/drivers/net/wireless/intel/iwlwifi/mvm/ops.c
Jiri Slaby 896490
+++ b/drivers/net/wireless/intel/iwlwifi/mvm/ops.c
Jiri Slaby 896490
@@ -1194,6 +1194,7 @@ iwl_op_mode_mvm_start(struct iwl_trans *trans, const struct iwl_cfg *cfg,
Jiri Slaby 896490
 	INIT_DELAYED_WORK(&mvm->scan_timeout_dwork, iwl_mvm_scan_timeout_wk);
Jiri Slaby 896490
 	INIT_WORK(&mvm->add_stream_wk, iwl_mvm_add_new_dqa_stream_wk);
Jiri Slaby 896490
 	INIT_LIST_HEAD(&mvm->add_stream_txqs);
Jiri Slaby 896490
+	spin_lock_init(&mvm->add_stream_lock);
Jiri Slaby 896490
 
Jiri Slaby 896490
 	init_waitqueue_head(&mvm->rx_sync_waitq);
Jiri Slaby 896490
 
Jiri Slaby 896490
diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/sta.c b/drivers/net/wireless/intel/iwlwifi/mvm/sta.c
Jiri Slaby 896490
index 21ad7b85..9caae779 100644
Jiri Slaby 896490
--- a/drivers/net/wireless/intel/iwlwifi/mvm/sta.c
Jiri Slaby 896490
+++ b/drivers/net/wireless/intel/iwlwifi/mvm/sta.c
Jiri Slaby 896490
@@ -384,8 +384,11 @@ static int iwl_mvm_disable_txq(struct iwl_mvm *mvm, struct ieee80211_sta *sta,
Jiri Slaby 896490
 		struct iwl_mvm_txq *mvmtxq =
Jiri Slaby 896490
 			iwl_mvm_txq_from_tid(sta, tid);
Jiri Slaby 896490
 
Jiri Slaby 896490
-		mvmtxq->txq_id = IWL_MVM_INVALID_QUEUE;
Jiri Slaby 896490
+		spin_lock_bh(&mvm->add_stream_lock);
Jiri Slaby 896490
 		list_del_init(&mvmtxq->list);
Jiri Slaby 896490
+		clear_bit(IWL_MVM_TXQ_STATE_READY, &mvmtxq->state);
Jiri Slaby 896490
+		mvmtxq->txq_id = IWL_MVM_INVALID_QUEUE;
Jiri Slaby 896490
+		spin_unlock_bh(&mvm->add_stream_lock);
Jiri Slaby 896490
 	}
Jiri Slaby 896490
 
Jiri Slaby 896490
 	/* Regardless if this is a reserved TXQ for a STA - mark it as false */
Jiri Slaby 896490
@@ -479,8 +482,11 @@ static int iwl_mvm_remove_sta_queue_marking(struct iwl_mvm *mvm, int queue)
Jiri Slaby 896490
 			disable_agg_tids |= BIT(tid);
Jiri Slaby 896490
 		mvmsta->tid_data[tid].txq_id = IWL_MVM_INVALID_QUEUE;
Jiri Slaby 896490
 
Jiri Slaby 896490
-		mvmtxq->txq_id = IWL_MVM_INVALID_QUEUE;
Jiri Slaby 896490
+		spin_lock_bh(&mvm->add_stream_lock);
Jiri Slaby 896490
 		list_del_init(&mvmtxq->list);
Jiri Slaby 896490
+		clear_bit(IWL_MVM_TXQ_STATE_READY, &mvmtxq->state);
Jiri Slaby 896490
+		mvmtxq->txq_id = IWL_MVM_INVALID_QUEUE;
Jiri Slaby 896490
+		spin_unlock_bh(&mvm->add_stream_lock);
Jiri Slaby 896490
 	}
Jiri Slaby 896490
 
Jiri Slaby 896490
 	mvmsta->tfd_queue_msk &= ~BIT(queue); /* Don't use this queue anymore */
Jiri Slaby 896490
@@ -1444,12 +1450,22 @@ void iwl_mvm_add_new_dqa_stream_wk(struct work_struct *wk)
Jiri Slaby 896490
 		 * a queue in the function itself.
Jiri Slaby 896490
 		 */
Jiri Slaby 896490
 		if (iwl_mvm_sta_alloc_queue(mvm, txq->sta, txq->ac, tid)) {
Jiri Slaby 896490
+			spin_lock_bh(&mvm->add_stream_lock);
Jiri Slaby 896490
 			list_del_init(&mvmtxq->list);
Jiri Slaby 896490
+			spin_unlock_bh(&mvm->add_stream_lock);
Jiri Slaby 896490
 			continue;
Jiri Slaby 896490
 		}
Jiri Slaby 896490
 
Jiri Slaby 896490
-		list_del_init(&mvmtxq->list);
Jiri Slaby 896490
+		/* now we're ready, any remaining races/concurrency will be
Jiri Slaby 896490
+		 * handled in iwl_mvm_mac_itxq_xmit()
Jiri Slaby 896490
+		 */
Jiri Slaby 896490
+		set_bit(IWL_MVM_TXQ_STATE_READY, &mvmtxq->state);
Jiri Slaby 896490
+
Jiri Slaby 896490
 		local_bh_disable();
Jiri Slaby 896490
+		spin_lock(&mvm->add_stream_lock);
Jiri Slaby 896490
+		list_del_init(&mvmtxq->list);
Jiri Slaby 896490
+		spin_unlock(&mvm->add_stream_lock);
Jiri Slaby 896490
+
Jiri Slaby 896490
 		iwl_mvm_mac_itxq_xmit(mvm->hw, txq);
Jiri Slaby 896490
 		local_bh_enable();
Jiri Slaby 896490
 	}
Jiri Slaby 896490
@@ -1864,8 +1880,11 @@ static void iwl_mvm_disable_sta_queues(struct iwl_mvm *mvm,
Jiri Slaby 896490
 		struct iwl_mvm_txq *mvmtxq =
Jiri Slaby 896490
 			iwl_mvm_txq_from_mac80211(sta->txq[i]);
Jiri Slaby 896490
 
Jiri Slaby 896490
+		spin_lock_bh(&mvm->add_stream_lock);
Jiri Slaby 896490
 		mvmtxq->txq_id = IWL_MVM_INVALID_QUEUE;
Jiri Slaby 896490
 		list_del_init(&mvmtxq->list);
Jiri Slaby 896490
+		clear_bit(IWL_MVM_TXQ_STATE_READY, &mvmtxq->state);
Jiri Slaby 896490
+		spin_unlock_bh(&mvm->add_stream_lock);
Jiri Slaby 896490
 	}
Jiri Slaby 896490
 }
Jiri Slaby 896490
 
Jiri Slaby 896490
-- 
Jiri Slaby 896490
2.35.3
Jiri Slaby 896490