All of lore.kernel.org
 help / color / mirror / Atom feed
From: Johannes Berg <johannes@sipsolutions.net>
To: linux-wireless@vger.kernel.org
Cc: David Miller <davem@davemloft.net>,
	Ron Rindjunsky <ron.rindjunsky@intel.com>
Subject: [RFC 3/3] mac80211: fix requeue race
Date: Sat, 19 Jul 2008 02:41:50 +0200	[thread overview]
Message-ID: <20080719004518.846156000@sipsolutions.net> (raw)
In-Reply-To: 20080719004147.795661000@sipsolutions.net

Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
---
 drivers/net/wireless/iwlwifi/iwl-tx.c |    4 
 include/net/mac80211.h                |   16 ---
 net/mac80211/ieee80211_i.h            |    7 +
 net/mac80211/main.c                   |  146 +++++++++++++++++++++-------------
 net/mac80211/wme.c                    |   76 ++++++++++++++++-
 net/mac80211/wme.h                    |    4 
 6 files changed, 175 insertions(+), 78 deletions(-)

--- everything.orig/net/mac80211/ieee80211_i.h	2008-07-19 01:37:10.000000000 +0200
+++ everything/net/mac80211/ieee80211_i.h	2008-07-19 02:04:39.000000000 +0200
@@ -534,8 +534,7 @@ struct ieee80211_sub_if_data *vif_to_sda
 enum {
 	IEEE80211_RX_MSG	= 1,
 	IEEE80211_TX_STATUS_MSG	= 2,
-	IEEE80211_DELBA_MSG	= 3,
-	IEEE80211_ADDBA_MSG	= 4,
+	IEEE80211_ADDBA_MSG	= 3,
 };
 
 /* maximum number of hardware queues we support. */
@@ -557,6 +556,10 @@ struct ieee80211_local {
 	 */
 	unsigned long queue_pool[BITS_TO_LONGS(QD_MAX_QUEUES)];
 
+	struct work_struct tx_ba_stop_work;
+	spinlock_t tx_ba_stop_lock;
+	struct list_head tx_ba_stop_list;
+
 	struct net_device *mdev; /* wmaster# - "master" 802.11 device */
 	int open_count;
 	int monitors, cooked_mntrs;
--- everything.orig/net/mac80211/main.c	2008-07-19 01:38:52.000000000 +0200
+++ everything/net/mac80211/main.c	2008-07-19 02:28:00.000000000 +0200
@@ -644,7 +644,7 @@ int ieee80211_start_tx_ba_session(struct
 		/* No need to requeue the packets in the agg queue, since we
 		 * haven't yet marked the queue in the queue_pool no packet
 		 * can have been enqueued to the newly allocated queue */
-		ieee80211_ht_agg_queue_remove(local, sta->tid_to_tx_q[tid], false);
+		ieee80211_ht_agg_queue_remove(local, sta->tid_to_tx_q[tid], -1);
 		sta->tid_to_tx_q[tid] = ieee80211_num_queues(hw);
 #ifdef CONFIG_MAC80211_HT_DEBUG
 		printk(KERN_DEBUG "BA request denied - HW unavailable for"
@@ -658,7 +658,7 @@ int ieee80211_start_tx_ba_session(struct
 	set_bit(reserved_queue, local->queue_pool);
 
 	/* Put all the packets onto the new aggregation queue */
-	ieee80211_requeue(local, ieee802_1d_to_ac[tid]);
+	ieee80211_requeue_toagg(local, ieee802_1d_to_ac[tid]);
 	spin_unlock_bh(&sta->lock);
 
 	/* send an addBA request */
@@ -805,9 +805,39 @@ void ieee80211_start_tx_ba_cb(struct iee
 }
 EXPORT_SYMBOL(ieee80211_start_tx_ba_cb);
 
-void ieee80211_stop_tx_ba_cb(struct ieee80211_hw *hw, u8 *ra, u8 tid)
+void ieee80211_start_tx_ba_cb_irqsafe(struct ieee80211_hw *hw,
+				      const u8 *ra, u16 tid)
 {
 	struct ieee80211_local *local = hw_to_local(hw);
+	struct ieee80211_ra_tid *ra_tid;
+	struct sk_buff *skb = dev_alloc_skb(0);
+
+	if (unlikely(!skb)) {
+#ifdef CONFIG_MAC80211_HT_DEBUG
+		if (net_ratelimit())
+			printk(KERN_WARNING "%s: Not enough memory, "
+			       "dropping start BA session", skb->dev->name);
+#endif
+		return;
+	}
+	ra_tid = (struct ieee80211_ra_tid *) &skb->cb;
+	memcpy(&ra_tid->ra, ra, ETH_ALEN);
+	ra_tid->tid = tid;
+
+	skb->pkt_type = IEEE80211_ADDBA_MSG;
+	skb_queue_tail(&local->skb_queue, skb);
+	tasklet_schedule(&local->tasklet);
+}
+EXPORT_SYMBOL(ieee80211_start_tx_ba_cb_irqsafe);
+
+struct tx_ba_work {
+	struct list_head list;
+	u8 ra[ETH_ALEN];
+	u8 tid;
+};
+
+static void ieee80211_stop_tx_ba_do(struct ieee80211_local *local, u8 *ra, u8 tid)
+{
 	struct sta_info *sta;
 	u8 *state;
 	int agg_queue;
@@ -856,15 +886,13 @@ void ieee80211_stop_tx_ba_cb(struct ieee
 
 	agg_queue = sta->tid_to_tx_q[tid];
 
-	ieee80211_ht_agg_queue_remove(local, agg_queue, true);
-	sta->tid_to_tx_q[tid] = ieee80211_num_queues(hw);
-
-	/* We just requeued the all the frames that were in the
-	 * removed queue, and since we might miss a softirq we do
-	 * netif_schedule_queue.  ieee80211_wake_queue is not used
-	 * here as this queue is not necessarily stopped
+	/*
+	 * From this point on, the queue will no longer be used to
+	 * queue frames on.
 	 */
-	netif_schedule_queue(netdev_get_tx_queue(local->mdev, agg_queue));
+	sta->tid_to_tx_q[tid] = ieee80211_num_queues(&local->hw);
+
+	/* Therefore, we can free the state now. */
 	spin_lock_bh(&sta->lock);
 	*state = HT_AGG_STATE_IDLE;
 	sta->ampdu_mlme.addba_req_num[tid] = 0;
@@ -873,58 +901,72 @@ void ieee80211_stop_tx_ba_cb(struct ieee
 	spin_unlock_bh(&sta->lock);
 
 	rcu_read_unlock();
+
+	/*
+	 * Now we need to synchronise so that the select_queue function
+	 * can no longer queue on the aggregation queue.
+	 */
+	synchronize_net();
+
+	/* Then, we can kill that queue. */
+	ieee80211_ht_agg_queue_remove(local, agg_queue, ieee802_1d_to_ac[tid]);
+
+	/*
+	 * We just requeued the all the frames that were in the
+	 * removed queue, and since we might miss a softirq we do
+	 * netif_schedule_queue.  ieee80211_wake_queue is not used
+	 * here as this queue is not necessarily stopped
+	 */
+	netif_schedule_queue(netdev_get_tx_queue(local->mdev, agg_queue));
 }
-EXPORT_SYMBOL(ieee80211_stop_tx_ba_cb);
 
-void ieee80211_start_tx_ba_cb_irqsafe(struct ieee80211_hw *hw,
-				      const u8 *ra, u16 tid)
+static void ieee80211_stop_tx_ba_work(struct work_struct *work)
 {
-	struct ieee80211_local *local = hw_to_local(hw);
-	struct ieee80211_ra_tid *ra_tid;
-	struct sk_buff *skb = dev_alloc_skb(0);
+	struct ieee80211_local *local;
+	struct tx_ba_work *todo;
+	unsigned long flags;
 
-	if (unlikely(!skb)) {
-#ifdef CONFIG_MAC80211_HT_DEBUG
-		if (net_ratelimit())
-			printk(KERN_WARNING "%s: Not enough memory, "
-			       "dropping start BA session", skb->dev->name);
-#endif
-		return;
-	}
-	ra_tid = (struct ieee80211_ra_tid *) &skb->cb;
-	memcpy(&ra_tid->ra, ra, ETH_ALEN);
-	ra_tid->tid = tid;
+	local = container_of(work, struct ieee80211_local, tx_ba_stop_work);
 
-	skb->pkt_type = IEEE80211_ADDBA_MSG;
-	skb_queue_tail(&local->skb_queue, skb);
-	tasklet_schedule(&local->tasklet);
+	spin_lock_irqsave(&local->tx_ba_stop_lock, flags);
+	while (1) {
+		if (list_empty(&local->tx_ba_stop_list))
+			break;
+		todo = list_first_entry(&local->tx_ba_stop_list,
+					struct tx_ba_work, list);
+		list_del(&todo->list);
+		spin_unlock_irqrestore(&local->tx_ba_stop_lock, flags);
+
+		ieee80211_stop_tx_ba_do(local, todo->ra, todo->tid);
+		kfree(todo);
+
+		spin_lock_irqsave(&local->tx_ba_stop_lock, flags);
+	}
+	spin_unlock_irqrestore(&local->tx_ba_stop_lock, flags);
 }
-EXPORT_SYMBOL(ieee80211_start_tx_ba_cb_irqsafe);
 
-void ieee80211_stop_tx_ba_cb_irqsafe(struct ieee80211_hw *hw,
-				     const u8 *ra, u16 tid)
+void ieee80211_stop_tx_ba_cb(struct ieee80211_hw *hw, const u8 *ra, u8 tid)
 {
 	struct ieee80211_local *local = hw_to_local(hw);
-	struct ieee80211_ra_tid *ra_tid;
-	struct sk_buff *skb = dev_alloc_skb(0);
+	struct tx_ba_work *todo = kzalloc(sizeof(*todo), GFP_ATOMIC);
+	unsigned long flags;
 
-	if (unlikely(!skb)) {
+	if (unlikely(!todo)) {
 #ifdef CONFIG_MAC80211_HT_DEBUG
-		if (net_ratelimit())
-			printk(KERN_WARNING "%s: Not enough memory, "
-			       "dropping stop BA session", skb->dev->name);
+		printk(KERN_WARNING "%s: Not enough memory, "
+		       "dropping stop BA session", wiphy_name(local->hw.wiphy));
 #endif
 		return;
 	}
-	ra_tid = (struct ieee80211_ra_tid *) &skb->cb;
-	memcpy(&ra_tid->ra, ra, ETH_ALEN);
-	ra_tid->tid = tid;
+	memcpy(todo->ra, ra, ETH_ALEN);
+	todo->tid = tid;
 
-	skb->pkt_type = IEEE80211_DELBA_MSG;
-	skb_queue_tail(&local->skb_queue, skb);
-	tasklet_schedule(&local->tasklet);
+	spin_lock_irqsave(&local->tx_ba_stop_lock, flags);
+	list_add_tail(&todo->list, &local->tx_ba_stop_list);
+	queue_work(local->hw.workqueue, &local->tx_ba_stop_work);
+	spin_unlock_irqrestore(&local->tx_ba_stop_lock, flags);
 }
-EXPORT_SYMBOL(ieee80211_stop_tx_ba_cb_irqsafe);
+EXPORT_SYMBOL(ieee80211_stop_tx_ba_cb);
 
 static void ieee80211_set_multicast_list(struct net_device *dev)
 {
@@ -1215,12 +1257,6 @@ static void ieee80211_tasklet_handler(un
 			skb->pkt_type = 0;
 			ieee80211_tx_status(local_to_hw(local), skb);
 			break;
-		case IEEE80211_DELBA_MSG:
-			ra_tid = (struct ieee80211_ra_tid *) &skb->cb;
-			ieee80211_stop_tx_ba_cb(local_to_hw(local),
-						ra_tid->ra, ra_tid->tid);
-			dev_kfree_skb(skb);
-			break;
 		case IEEE80211_ADDBA_MSG:
 			ra_tid = (struct ieee80211_ra_tid *) &skb->cb;
 			ieee80211_start_tx_ba_cb(local_to_hw(local),
@@ -1599,6 +1635,10 @@ struct ieee80211_hw *ieee80211_alloc_hw(
 
 	INIT_DELAYED_WORK(&local->scan_work, ieee80211_sta_scan_work);
 
+	INIT_LIST_HEAD(&local->tx_ba_stop_list);
+	spin_lock_init(&local->tx_ba_stop_lock);
+	INIT_WORK(&local->tx_ba_stop_work, ieee80211_stop_tx_ba_work);
+
 	sta_info_init(local);
 
 	tasklet_init(&local->tx_pending_tasklet, ieee80211_tx_pending,
--- everything.orig/include/net/mac80211.h	2008-07-19 02:03:18.000000000 +0200
+++ everything/include/net/mac80211.h	2008-07-19 02:07:08.000000000 +0200
@@ -1734,21 +1734,9 @@ int ieee80211_stop_tx_ba_session(struct 
  *
  * This function must be called by low level driver once it has
  * finished with preparations for the BA session tear down.
+ * This function can be called in any context.
  */
-void ieee80211_stop_tx_ba_cb(struct ieee80211_hw *hw, u8 *ra, u8 tid);
-
-/**
- * ieee80211_stop_tx_ba_cb_irqsafe - low level driver ready to stop aggregate.
- * @hw: pointer as obtained from ieee80211_alloc_hw().
- * @ra: receiver address of the BA session recipient.
- * @tid: the desired TID to BA on.
- *
- * This function must be called by low level driver once it has
- * finished with preparations for the BA session tear down.
- * This version of the function is IRQ-safe.
- */
-void ieee80211_stop_tx_ba_cb_irqsafe(struct ieee80211_hw *hw, const u8 *ra,
-				     u16 tid);
+void ieee80211_stop_tx_ba_cb(struct ieee80211_hw *hw, const u8 *ra, u8 tid);
 
 /**
  * ieee80211_notify_mac - low level driver notification
--- everything.orig/drivers/net/wireless/iwlwifi/iwl-tx.c	2008-07-19 02:10:42.000000000 +0200
+++ everything/drivers/net/wireless/iwlwifi/iwl-tx.c	2008-07-19 02:10:46.000000000 +0200
@@ -1304,7 +1304,7 @@ int iwl_tx_agg_stop(struct iwl_priv *pri
 	if (ret)
 		return ret;
 
-	ieee80211_stop_tx_ba_cb_irqsafe(priv->hw, ra, tid);
+	ieee80211_stop_tx_ba_cb(priv->hw, ra, tid);
 
 	return 0;
 }
@@ -1328,7 +1328,7 @@ int iwl_txq_check_empty(struct iwl_priv 
 			priv->cfg->ops->lib->txq_agg_disable(priv, txq_id,
 							     ssn, tx_fifo);
 			tid_data->agg.state = IWL_AGG_OFF;
-			ieee80211_stop_tx_ba_cb_irqsafe(priv->hw, addr, tid);
+			ieee80211_stop_tx_ba_cb(priv->hw, addr, tid);
 		}
 		break;
 	case IWL_EMPTYING_HW_QUEUE_ADDBA:
--- everything.orig/net/mac80211/wme.c	2008-07-19 02:14:14.000000000 +0200
+++ everything/net/mac80211/wme.c	2008-07-19 02:36:01.000000000 +0200
@@ -222,17 +222,83 @@ int ieee80211_ht_agg_queue_add(struct ie
 	return -EAGAIN;
 }
 
+static void ieee80211_requeue_fromagg(struct ieee80211_local *local,
+				      int queue, u16 new_queue)
+{
+	struct netdev_queue *from_txq = netdev_get_tx_queue(local->mdev, queue);
+	struct netdev_queue *to_txq;
+	struct sk_buff_head list;
+	spinlock_t *from_root_lock = NULL, *to_root_lock;
+	struct Qdisc *qdisc, *to_qdisc = NULL;
+	u32 len;
+
+	rcu_read_lock_bh();
+
+	qdisc = rcu_dereference(from_txq->qdisc);
+	if (!qdisc || !qdisc->dequeue)
+		goto out_unlock;
+
+	to_txq = netdev_get_tx_queue(local->mdev, new_queue);
+	to_qdisc = rcu_dereference(to_txq->qdisc);
+	if (!to_qdisc || !to_qdisc->enqueue)
+		goto out_unlock;
+
+	to_root_lock = qdisc_root_lock(to_qdisc);
+
+	if (to_qdisc != qdisc)
+		from_root_lock = qdisc_root_lock(qdisc);
+
+	skb_queue_head_init(&list);
+
+	spin_lock(to_root_lock);
+
+	if (from_root_lock) {
+		spin_lock(from_root_lock);
+		for (len = qdisc->q.qlen; len > 0; len--) {
+			struct sk_buff *skb = qdisc->dequeue(qdisc);
+
+			if (skb)
+				__skb_queue_tail(&list, skb);
+		}
+		spin_unlock(from_root_lock);
+	} else {
+		for (len = qdisc->q.qlen; len > 0; len--) {
+			struct sk_buff *skb = qdisc->dequeue(qdisc);
+
+			if (skb)
+				__skb_queue_tail(&list, skb);
+		}
+	}
+
+	for (len = list.qlen; len > 0; len--) {
+		struct sk_buff *skb = __skb_dequeue(&list);
+
+		BUG_ON(!skb);
+		skb_set_queue_mapping(skb, new_queue);
+
+		to_qdisc->enqueue(skb, qdisc);
+	}
+
+	spin_unlock(to_root_lock);
+
+ out_unlock:
+	rcu_read_unlock_bh();
+}
+
 /**
  * the caller needs to hold netdev_get_tx_queue(local->mdev, X)->lock
+ *
+ * regular_queue >= 0 indicates that requeueing must be done and will
+ * go to that queue.
  */
 void ieee80211_ht_agg_queue_remove(struct ieee80211_local *local,
-				   u16 agg_queue, bool requeue)
+				   u16 agg_queue, int regular_queue)
 {
 	/* return the qdisc to the pool */
 	clear_bit(agg_queue, local->queue_pool);
 
-	if (requeue) {
-		ieee80211_requeue(local, agg_queue);
+	if (regular_queue >= 0) {
+		ieee80211_requeue_fromagg(local, agg_queue, regular_queue);
 	} else {
 		struct netdev_queue *txq;
 		spinlock_t *root_lock;
@@ -246,7 +312,7 @@ void ieee80211_ht_agg_queue_remove(struc
 	}
 }
 
-void ieee80211_requeue(struct ieee80211_local *local, int queue)
+void ieee80211_requeue_toagg(struct ieee80211_local *local, int queue)
 {
 	struct netdev_queue *txq = netdev_get_tx_queue(local->mdev, queue);
 	struct sk_buff_head list;
@@ -291,6 +357,6 @@ void ieee80211_requeue(struct ieee80211_
 		spin_unlock(root_lock);
 	}
 
-out_unlock:
+ out_unlock:
 	rcu_read_unlock_bh();
 }
--- everything.orig/net/mac80211/wme.h	2008-07-19 02:14:37.000000000 +0200
+++ everything/net/mac80211/wme.h	2008-07-19 02:28:12.000000000 +0200
@@ -27,7 +27,7 @@ u16 ieee80211_select_queue(struct net_de
 int ieee80211_ht_agg_queue_add(struct ieee80211_local *local,
 			       struct sta_info *sta, u16 tid);
 void ieee80211_ht_agg_queue_remove(struct ieee80211_local *local,
-				   u16 agg_queue, bool requeue);
-void ieee80211_requeue(struct ieee80211_local *local, int queue);
+				   u16 agg_queue, int regular_queue);
+void ieee80211_requeue_toagg(struct ieee80211_local *local, int from);
 
 #endif /* _WME_H */

-- 


  parent reply	other threads:[~2008-07-19  0:45 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2008-07-19  0:41 [RFC 0/3] mac80211 tx mq aggregation fixes Johannes Berg
2008-07-19  0:41 ` [RFC 1/3] mac80211: sane arguments to ieee80211_ht_agg_queue_remove Johannes Berg
2008-07-19  0:41 ` [RFC 2/3] mac80211: fix race during adding HT queues Johannes Berg
2008-07-19  0:41 ` Johannes Berg [this message]
2008-07-19  0:52   ` [RFC 3/3] mac80211: fix requeue race Johannes Berg

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20080719004518.846156000@sipsolutions.net \
    --to=johannes@sipsolutions.net \
    --cc=davem@davemloft.net \
    --cc=linux-wireless@vger.kernel.org \
    --cc=ron.rindjunsky@intel.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.